From f0278ca21a7b9941797608d310b8ab831135a21f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 17 Feb 2026 21:33:20 +0800 Subject: [PATCH 01/25] chore(deps-dev): bump elysia from 1.4.13 to 1.4.18 (#2384) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- packages/server/package.json | 2 +- pnpm-lock.yaml | 283 ++++++++++++++++++++++------------- 2 files changed, 179 insertions(+), 106 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index f441c136c..3242f0ba9 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -140,7 +140,7 @@ "@zenstackhq/typescript-config": "workspace:*", "@zenstackhq/vitest-config": "workspace:*", "body-parser": "^2.2.0", - "elysia": "^1.3.1", + "elysia": "^1.4.18", "express": "^5.0.0", "fastify": "^5.6.1", "fastify-plugin": "^5.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6e6a972e..45ac2f741 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -729,8 +729,8 @@ importers: specifier: ^2.2.0 version: 2.2.0 elysia: - specifier: ^1.3.1 - version: 1.4.13(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3) + specifier: ^1.4.18 + version: 1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3) express: specifier: ^5.0.0 version: 5.1.0 @@ -1215,18 +1215,18 @@ importers: packages: - '@acemir/cssom@0.9.23': - resolution: {integrity: sha512-2kJ1HxBKzPLbmhZpxBiTZggjtgCwKg1ma5RHShxvd6zgqhDEdEkzpiwe7jLkI2p2BrZvFCXIihdoMkl1H39VnA==} + '@acemir/cssom@0.9.31': + resolution: {integrity: sha512-ZnR3GSaH+/vJ0YlHau21FjfLYjMpYVIzTD8M8vIEQvIGxeOXyXdzCI140rrCY862p/C/BbzWsjc1dgnM9mkoTA==} '@alloc/quick-lru@5.2.0': resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} engines: {node: '>=10'} - '@asamuzakjp/css-color@4.0.5': - resolution: {integrity: sha512-lMrXidNhPGsDjytDy11Vwlb6OIGrT3CmLg3VWNFyWkLWtijKl7xjvForlh8vuj0SHGjgl4qZEQzUmYTeQA2JFQ==} + '@asamuzakjp/css-color@4.1.2': + resolution: {integrity: sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==} - '@asamuzakjp/dom-selector@6.7.4': - resolution: {integrity: sha512-buQDjkm+wDPXd6c13534URWZqbz0RP5PAhXZ+LIoa5LgwInT9HVJvGIJivg75vi8I13CxDGdTnz+aY5YUJlIAA==} + '@asamuzakjp/dom-selector@6.8.1': + resolution: {integrity: sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==} '@asamuzakjp/nwsapi@2.3.9': resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} @@ -1345,6 +1345,11 @@ packages: engines: {node: '>=6.0.0'} hasBin: true + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + '@babel/plugin-syntax-jsx@7.27.1': resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} engines: {node: '>=6.9.0'} @@ -1439,6 +1444,10 @@ packages: resolution: {integrity: sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==} engines: {node: '>=6.9.0'} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@bcoe/v8-coverage@1.0.2': resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} @@ -1483,8 +1492,8 @@ packages: commander: optional: true - '@borewit/text-codec@0.1.1': - resolution: {integrity: sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==} + '@borewit/text-codec@0.2.1': + resolution: {integrity: sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==} '@chevrotain/cst-dts-gen@10.5.0': resolution: {integrity: sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==} @@ -1529,37 +1538,36 @@ packages: resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} engines: {node: '>=18.0.0'} - '@csstools/color-helpers@5.1.0': - resolution: {integrity: sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==} - engines: {node: '>=18'} + '@csstools/color-helpers@6.0.1': + resolution: {integrity: sha512-NmXRccUJMk2AWA5A7e5a//3bCIMyOu2hAtdRYrhPPHjDxINuCwX1w6rnIZ4xjLcp0ayv6h8Pc3X0eJUGiAAXHQ==} + engines: {node: '>=20.19.0'} - '@csstools/css-calc@2.1.4': - resolution: {integrity: sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==} - engines: {node: '>=18'} + '@csstools/css-calc@3.1.1': + resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-color-parser@3.1.0': - resolution: {integrity: sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==} - engines: {node: '>=18'} + '@csstools/css-color-parser@4.0.1': + resolution: {integrity: sha512-vYwO15eRBEkeF6xjAno/KQ61HacNhfQuuU/eGwH67DplL0zD5ZixUa563phQvUelA07yDczIXdtmYojCphKJcw==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-parser-algorithms': ^3.0.5 - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-parser-algorithms@3.0.5': - resolution: {integrity: sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==} - engines: {node: '>=18'} + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} peerDependencies: - '@csstools/css-tokenizer': ^3.0.4 + '@csstools/css-tokenizer': ^4.0.0 - '@csstools/css-syntax-patches-for-csstree@1.0.16': - resolution: {integrity: sha512-2SpS4/UaWQaGpBINyG5ZuCHnUDeVByOhvbkARwfmnfxDvTaj80yOI1cD8Tw93ICV5Fx4fnyDKWQZI1CDtcWyUg==} - engines: {node: '>=18'} + '@csstools/css-syntax-patches-for-csstree@1.0.27': + resolution: {integrity: sha512-sxP33Jwg1bviSUXAV43cVYdmjt2TLnLXNqCWl9xmxHawWVjGz/kEbdkr7F9pxJNBN2Mh+dq0crgItbW6tQvyow==} - '@csstools/css-tokenizer@3.0.4': - resolution: {integrity: sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==} - engines: {node: '>=18'} + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} '@dxup/nuxt@0.2.2': resolution: {integrity: sha512-RNpJjDZs9+JcT9N87AnOuHsNM75DEd58itADNd/s1LIF6BZbTLZV0xxilJZb55lntn4TYvscTaXLCBX2fq9CXg==} @@ -3207,6 +3215,11 @@ packages: peerDependencies: acorn: ^8.9.0 + '@sveltejs/acorn-typescript@1.0.9': + resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} + peerDependencies: + acorn: ^8.9.0 + '@sveltejs/adapter-auto@7.0.0': resolution: {integrity: sha512-ImDWaErTOCkRS4Gt+5gZuymKFBobnhChXUZ9lhUZLahUgvA4OOvRzi3sahzYgbxGj5nkA6OV0GAW378+dl/gyw==} peerDependencies: @@ -4691,8 +4704,8 @@ packages: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} - cookie@1.0.2: - resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} engines: {node: '>=18'} cookiejar@2.1.4: @@ -4784,8 +4797,8 @@ packages: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} - cssstyle@5.3.3: - resolution: {integrity: sha512-OytmFH+13/QXONJcC75QNdMtKpceNk3u8ThBjyyYjkEcy/ekBwR1mMAuNvi3gdBPW3N5TlCzQ0WZw8H0lN/bDw==} + cssstyle@5.3.7: + resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==} engines: {node: '>=20'} csstype@3.2.3: @@ -4798,8 +4811,8 @@ packages: damerau-levenshtein@1.0.8: resolution: {integrity: sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==} - data-urls@6.0.0: - resolution: {integrity: sha512-BnBS08aLUM+DKamupXs3w2tJJoqU+AkaE/+6vQxi/G/DPmIZFJJp9Dkb1kM03AZx8ADehDUZgsNxju3mPXZYIA==} + data-urls@6.0.1: + resolution: {integrity: sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==} engines: {node: '>=20'} data-view-buffer@1.0.2: @@ -4952,6 +4965,9 @@ packages: devalue@5.6.1: resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} + devalue@5.6.2: + resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} + dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} @@ -5102,8 +5118,8 @@ packages: electron-to-chromium@1.5.267: resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} - elysia@1.4.13: - resolution: {integrity: sha512-6QaWQEm7QN1UCo1TPpEjaRJPHUmnM7R29y6LY224frDGk5PrpAnWmdHkoZxkcv+JRWp1j2ROr2IHbxHbG/jRjw==} + elysia@1.4.18: + resolution: {integrity: sha512-A6BhlipmSvgCy69SBgWADYZSdDIj3fT2gk8/9iMAC8iD+aGcnCr0fitziX0xr36MFDs/fsvVp8dWqxeq1VCgKg==} peerDependencies: '@sinclair/typebox': '>= 0.34.0 < 1' '@types/bun': '>= 1.2.0' @@ -5338,6 +5354,9 @@ packages: esrap@2.2.1: resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} + esrap@2.2.3: + resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} + esrecurse@4.3.0: resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} engines: {node: '>=4.0'} @@ -5634,11 +5653,13 @@ packages: glob@10.4.5: resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@11.1.0: resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==} engines: {node: 20 || >=22} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true global-directory@4.0.1: @@ -6368,6 +6389,10 @@ packages: resolution: {integrity: sha512-F9ODfyqML2coTIsQpSkRHnLSZMtkU8Q+mSfcaIyKwy58u+8k5nvAYeiNhsyMARvzNcXJ9QfWVrcPsC9e9rAxtg==} engines: {node: 20 || >=22} + lru-cache@11.2.6: + resolution: {integrity: sha512-ESL2CrkS/2wTPfuend7Zhkzo2u0daGJ/A2VucJOgQ/C48S/zB8MMeMHSGKYpXhIjbPxfuezITkaBH1wqv00DDQ==} + engines: {node: 20 || >=22} + lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -8016,6 +8041,7 @@ packages: tar@7.5.1: resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} engines: {node: '>=18'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me terser@5.44.0: resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} @@ -8063,11 +8089,11 @@ packages: resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} engines: {node: '>=14.0.0'} - tldts-core@7.0.17: - resolution: {integrity: sha512-DieYoGrP78PWKsrXr8MZwtQ7GLCUeLxihtjC1jZsW1DnvSMdKPitJSe8OSYDM2u5H6g3kWJZpePqkp43TfLh0g==} + tldts-core@7.0.23: + resolution: {integrity: sha512-0g9vrtDQLrNIiCj22HSe9d4mLVG3g5ph5DZ8zCKBr4OtrspmNB6ss7hVyzArAeE88ceZocIEGkyW1Ime7fxPtQ==} - tldts@7.0.17: - resolution: {integrity: sha512-Y1KQBgDd/NUc+LfOtKS6mNsC9CCaH+m2P1RoIZy7RAPo3C3/t8X45+zgut31cRZtZ3xKPjfn3TkGTrctC2TQIQ==} + tldts@7.0.23: + resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} hasBin: true tmp@0.2.3: @@ -8086,8 +8112,8 @@ packages: resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} engines: {node: '>=0.6'} - token-types@6.1.1: - resolution: {integrity: sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==} + token-types@6.1.2: + resolution: {integrity: sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==} engines: {node: '>=14.16'} toposort@2.0.2: @@ -8661,8 +8687,8 @@ packages: webidl-conversions@4.0.2: resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==} - webidl-conversions@8.0.0: - resolution: {integrity: sha512-n4W4YFyz5JzOfQeA8oN7dUYpR+MBP3PIUsn2jLjWXwK5ASUzt0Jc/A5sAUZoCYFJRGF0FBKJ+1JjN43rNdsQzA==} + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} webpack-virtual-modules@0.6.2: @@ -8681,6 +8707,10 @@ packages: resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==} engines: {node: '>=18'} + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + whatwg-url@15.1.0: resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==} engines: {node: '>=20'} @@ -8753,6 +8783,18 @@ packages: utf-8-validate: optional: true + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + wsl-utils@0.1.0: resolution: {integrity: sha512-h3Fbisa2nKGPxCpm89Hk33lBLsnaGBvctQopaBSOW/uIs6FTe1ATyAnKFJrzVs9vpGdsTe73WF3V4lIsk4Gacw==} engines: {node: '>=18'} @@ -8836,27 +8878,27 @@ packages: snapshots: - '@acemir/cssom@0.9.23': + '@acemir/cssom@0.9.31': optional: true '@alloc/quick-lru@5.2.0': {} - '@asamuzakjp/css-color@4.0.5': + '@asamuzakjp/css-color@4.1.2': dependencies: - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-color-parser': 3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 - lru-cache: 11.2.2 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + lru-cache: 11.2.6 optional: true - '@asamuzakjp/dom-selector@6.7.4': + '@asamuzakjp/dom-selector@6.8.1': dependencies: '@asamuzakjp/nwsapi': 2.3.9 bidi-js: 1.0.3 css-tree: 3.1.0 is-potential-custom-element-name: 1.0.1 - lru-cache: 11.2.2 + lru-cache: 11.2.6 optional: true '@asamuzakjp/nwsapi@2.3.9': @@ -9021,6 +9063,11 @@ snapshots: dependencies: '@babel/types': 7.28.6 + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + optional: true + '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': dependencies: '@babel/core': 7.28.5 @@ -9155,6 +9202,12 @@ snapshots: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + optional: true + '@bcoe/v8-coverage@1.0.2': {} '@better-auth/cli@1.4.17(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.1.2)(kysely@0.28.8)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.0.1)(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.46.1)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': @@ -9255,7 +9308,7 @@ snapshots: cac: 6.7.14 citty: 0.1.6 - '@borewit/text-codec@0.1.1': {} + '@borewit/text-codec@0.2.1': {} '@chevrotain/cst-dts-gen@10.5.0': dependencies: @@ -9315,32 +9368,32 @@ snapshots: dependencies: mime: 3.0.0 - '@csstools/color-helpers@5.1.0': + '@csstools/color-helpers@6.0.1': optional: true - '@csstools/css-calc@2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 optional: true - '@csstools/css-color-parser@3.1.0(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-color-parser@4.0.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/color-helpers': 5.1.0 - '@csstools/css-calc': 2.1.4(@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4))(@csstools/css-tokenizer@3.0.4) - '@csstools/css-parser-algorithms': 3.0.5(@csstools/css-tokenizer@3.0.4) - '@csstools/css-tokenizer': 3.0.4 + '@csstools/color-helpers': 6.0.1 + '@csstools/css-calc': 3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 optional: true - '@csstools/css-parser-algorithms@3.0.5(@csstools/css-tokenizer@3.0.4)': + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': dependencies: - '@csstools/css-tokenizer': 3.0.4 + '@csstools/css-tokenizer': 4.0.0 optional: true - '@csstools/css-syntax-patches-for-csstree@1.0.16': + '@csstools/css-syntax-patches-for-csstree@1.0.27': optional: true - '@csstools/css-tokenizer@3.0.4': + '@csstools/css-tokenizer@4.0.0': optional: true '@dxup/nuxt@0.2.2(magicast@0.5.1)': @@ -10737,6 +10790,10 @@ snapshots: dependencies: acorn: 8.15.0 + '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)': + dependencies: + acorn: 8.15.0 + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))': dependencies: '@sveltejs/kit': 2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) @@ -11099,7 +11156,7 @@ snapshots: dependencies: debug: 4.4.3 fflate: 0.8.2 - token-types: 6.1.1 + token-types: 6.1.2 transitivePeerDependencies: - supports-color @@ -12422,7 +12479,7 @@ snapshots: cookie@0.7.1: {} - cookie@1.0.2: {} + cookie@1.1.1: {} cookiejar@2.1.4: {} @@ -12542,11 +12599,12 @@ snapshots: dependencies: css-tree: 2.2.1 - cssstyle@5.3.3: + cssstyle@5.3.7: dependencies: - '@asamuzakjp/css-color': 4.0.5 - '@csstools/css-syntax-patches-for-csstree': 1.0.16 + '@asamuzakjp/css-color': 4.1.2 + '@csstools/css-syntax-patches-for-csstree': 1.0.27 css-tree: 3.1.0 + lru-cache: 11.2.6 optional: true csstype@3.2.3: {} @@ -12555,9 +12613,9 @@ snapshots: damerau-levenshtein@1.0.8: {} - data-urls@6.0.0: + data-urls@6.0.1: dependencies: - whatwg-mimetype: 4.0.0 + whatwg-mimetype: 5.0.0 whatwg-url: 15.1.0 optional: true @@ -12660,6 +12718,8 @@ snapshots: devalue@5.6.1: {} + devalue@5.6.2: {} + dezalgo@1.0.4: dependencies: asap: 2.0.6 @@ -12734,10 +12794,10 @@ snapshots: electron-to-chromium@1.5.267: {} - elysia@1.4.13(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3): + elysia@1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3): dependencies: '@sinclair/typebox': 0.34.41 - cookie: 1.0.2 + cookie: 1.1.1 exact-mirror: 0.2.2(@sinclair/typebox@0.34.41) fast-decode-uri-component: 1.0.1 file-type: 21.0.0 @@ -12983,7 +13043,7 @@ snapshots: eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.6.1)) @@ -13016,7 +13076,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -13031,7 +13091,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13179,6 +13239,10 @@ snapshots: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 + esrap@2.2.3: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + esrecurse@4.3.0: dependencies: estraverse: 5.3.0 @@ -13371,7 +13435,7 @@ snapshots: dependencies: '@tokenizer/inflate': 0.2.7 strtok3: 10.3.4 - token-types: 6.1.1 + token-types: 6.1.2 uint8array-extras: 1.5.0 transitivePeerDependencies: - supports-color @@ -14009,10 +14073,10 @@ snapshots: jsdom@27.1.0: dependencies: - '@acemir/cssom': 0.9.23 - '@asamuzakjp/dom-selector': 6.7.4 - cssstyle: 5.3.3 - data-urls: 6.0.0 + '@acemir/cssom': 0.9.31 + '@asamuzakjp/dom-selector': 6.8.1 + cssstyle: 5.3.7 + data-urls: 6.0.1 decimal.js: 10.6.0 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 @@ -14023,11 +14087,11 @@ snapshots: symbol-tree: 3.2.4 tough-cookie: 6.0.0 w3c-xmlserializer: 5.0.0 - webidl-conversions: 8.0.0 + webidl-conversions: 8.0.1 whatwg-encoding: 3.1.1 whatwg-mimetype: 4.0.0 whatwg-url: 15.1.0 - ws: 8.18.3 + ws: 8.19.0 xml-name-validator: 5.0.0 transitivePeerDependencies: - bufferutil @@ -14148,7 +14212,7 @@ snapshots: light-my-request@6.6.0: dependencies: - cookie: 1.0.2 + cookie: 1.1.1 process-warning: 4.0.1 set-cookie-parser: 2.7.2 @@ -14284,6 +14348,9 @@ snapshots: lru-cache@11.2.2: {} + lru-cache@11.2.6: + optional: true + lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -14316,8 +14383,8 @@ snapshots: magicast@0.3.5: dependencies: - '@babel/parser': 7.28.6 - '@babel/types': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 source-map-js: 1.2.1 optional: true @@ -16185,15 +16252,15 @@ snapshots: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) '@types/estree': 1.0.8 acorn: 8.15.0 aria-query: 5.3.2 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.6.1 + devalue: 5.6.2 esm-env: 1.2.2 - esrap: 2.2.1 + esrap: 2.2.3 is-reference: 3.0.3 locate-character: 3.0.0 magic-string: 0.30.21 @@ -16301,12 +16368,12 @@ snapshots: tinyrainbow@3.0.3: {} - tldts-core@7.0.17: + tldts-core@7.0.23: optional: true - tldts@7.0.17: + tldts@7.0.23: dependencies: - tldts-core: 7.0.17 + tldts-core: 7.0.23 optional: true tmp@0.2.3: {} @@ -16319,9 +16386,9 @@ snapshots: toidentifier@1.0.1: {} - token-types@6.1.1: + token-types@6.1.2: dependencies: - '@borewit/text-codec': 0.1.1 + '@borewit/text-codec': 0.2.1 '@tokenizer/token': 0.3.0 ieee754: 1.2.1 @@ -16331,7 +16398,7 @@ snapshots: tough-cookie@6.0.0: dependencies: - tldts: 7.0.17 + tldts: 7.0.23 optional: true tr46@0.0.3: {} @@ -16968,7 +17035,7 @@ snapshots: webidl-conversions@4.0.2: {} - webidl-conversions@8.0.0: + webidl-conversions@8.0.1: optional: true webpack-virtual-modules@0.6.2: {} @@ -16983,10 +17050,13 @@ snapshots: whatwg-mimetype@4.0.0: optional: true + whatwg-mimetype@5.0.0: + optional: true + whatwg-url@15.1.0: dependencies: tr46: 6.0.0 - webidl-conversions: 8.0.0 + webidl-conversions: 8.0.1 optional: true whatwg-url@5.0.0: @@ -17076,6 +17146,9 @@ snapshots: ws@8.18.3: {} + ws@8.19.0: + optional: true + wsl-utils@0.1.0: dependencies: is-wsl: 3.1.0 From da3b0af3430a98ee1cfd9280d34bf3c1c0c1d7b3 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 18 Feb 2026 23:45:04 +0800 Subject: [PATCH 02/25] feat: ORM api slicing (#2383) --- .../auth-adapters/better-auth/src/adapter.ts | 2 +- .../tanstack-query/src/common/types.ts | 27 +- packages/clients/tanstack-query/src/react.ts | 108 +- .../tanstack-query/src/svelte/index.svelte.ts | 80 +- packages/clients/tanstack-query/src/vue.ts | 80 +- .../test/react-sliced-client.test-d.ts | 93 + .../tanstack-query/test/react-typing-test.ts | 143 -- .../test/react-typing.test-d.ts | 153 ++ .../test/svelte-sliced-client.test-d.ts | 93 + .../test/vue-sliced-client.test-d.ts | 93 + .../clients/tanstack-query/vitest.config.ts | 4 + packages/orm/src/client/client-impl.ts | 103 +- packages/orm/src/client/constants.ts | 55 +- packages/orm/src/client/contract.ts | 116 +- packages/orm/src/client/crud-types.ts | 1052 +++++---- .../src/client/crud/dialects/base-dialect.ts | 15 +- .../dialects/lateral-join-dialect-base.ts | 10 +- .../orm/src/client/crud/dialects/sqlite.ts | 4 +- .../orm/src/client/crud/operations/base.ts | 8 +- .../orm/src/client/crud/operations/create.ts | 15 +- .../orm/src/client/crud/operations/delete.ts | 5 +- .../orm/src/client/crud/operations/find.ts | 5 +- .../orm/src/client/crud/operations/update.ts | 13 +- .../orm/src/client/crud/validator/index.ts | 595 +++-- packages/orm/src/client/index.ts | 1 + packages/orm/src/client/options.ts | 154 +- packages/orm/src/client/query-utils.ts | 4 +- packages/orm/src/client/type-utils.ts | 262 +++ packages/zod/package.json | 4 +- tests/e2e/orm/client-api/slicing.test.ts | 2001 +++++++++++++++++ tests/e2e/orm/schemas/basic/schema.ts | 5 + tests/e2e/orm/schemas/basic/schema.zmodel | 1 + 32 files changed, 4332 insertions(+), 972 deletions(-) create mode 100644 packages/clients/tanstack-query/test/react-sliced-client.test-d.ts delete mode 100644 packages/clients/tanstack-query/test/react-typing-test.ts create mode 100644 packages/clients/tanstack-query/test/react-typing.test-d.ts create mode 100644 packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts create mode 100644 packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts create mode 100644 packages/orm/src/client/type-utils.ts create mode 100644 tests/e2e/orm/client-api/slicing.test.ts diff --git a/packages/auth-adapters/better-auth/src/adapter.ts b/packages/auth-adapters/better-auth/src/adapter.ts index 935f5fde3..5e1138f63 100644 --- a/packages/auth-adapters/better-auth/src/adapter.ts +++ b/packages/auth-adapters/better-auth/src/adapter.ts @@ -171,7 +171,7 @@ export const zenstackAdapter = (db: ClientContract>, + data: update as UpdateInput, any>, }); }, diff --git a/packages/clients/tanstack-query/src/common/types.ts b/packages/clients/tanstack-query/src/common/types.ts index 2e61d286d..f745ceeb4 100644 --- a/packages/clients/tanstack-query/src/common/types.ts +++ b/packages/clients/tanstack-query/src/common/types.ts @@ -1,6 +1,12 @@ import type { Logger, OptimisticDataProvider } from '@zenstackhq/client-helpers'; import type { FetchFn } from '@zenstackhq/client-helpers/fetch'; -import type { GetProcedureNames, OperationsIneligibleForDelegateModels, ProcedureFunc } from '@zenstackhq/orm'; +import type { + GetProcedureNames, + GetSlicedOperations, + OperationsIneligibleForDelegateModels, + ProcedureFunc, + QueryOptions, +} from '@zenstackhq/orm'; import type { GetModels, IsDelegateModel, SchemaDef } from '@zenstackhq/schema'; /** @@ -57,14 +63,27 @@ type HooksOperationsIneligibleForDelegateModels = OperationsIneligibleForDelegat ? `use${Capitalize}` : never; +type Modifiers = '' | 'Suspense' | 'Infinite' | 'SuspenseInfinite'; + /** - * Trim operations that are ineligible for delegate models from the given model operations type. + * Trim CRUD operation hooks to include only eligible operations. */ -export type TrimDelegateModelOperations< +export type TrimSlicedOperations< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions, T extends Record, -> = IsDelegateModel extends true ? Omit : T; +> = { + // trim operations based on slicing options + [Key in keyof T as Key extends `use${Modifiers}${Capitalize>}` + ? IsDelegateModel extends true + ? // trim operations ineligible for delegate models + Key extends HooksOperationsIneligibleForDelegateModels + ? never + : Key + : Key + : never]: T[Key]; +}; type WithOptimisticFlag = T extends object ? T & { diff --git a/packages/clients/tanstack-query/src/react.ts b/packages/clients/tanstack-query/src/react.ts index f467f116a..5129750bf 100644 --- a/packages/clients/tanstack-query/src/react.ts +++ b/packages/clients/tanstack-query/src/react.ts @@ -39,6 +39,8 @@ import type { FindUniqueArgs, GetProcedure, GetProcedureNames, + GetSlicedModels, + GetSlicedProcedures, GroupByArgs, GroupByResult, ProcedureEnvelope, @@ -62,7 +64,7 @@ import type { ExtraQueryOptions, ProcedureReturn, QueryContext, - TrimDelegateModelOperations, + TrimSlicedOperations, WithOptimistic, } from './common/types.js'; export type { FetchFn } from '@zenstackhq/client-helpers/fetch'; @@ -148,11 +150,11 @@ export type ModelMutationModelResult< }; export type ClientHooks = QueryOptions> = { - [Model in GetModels as `${Uncapitalize}`]: ModelQueryHooks; -} & ProcedureHooks; + [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks; +} & ProcedureHooks; -type ProcedureHookGroup = { - [Name in GetProcedureNames]: GetProcedure extends { mutation: true } +type ProcedureHookGroup> = { + [Name in GetSlicedProcedures]: GetProcedure extends { mutation: true } ? { useMutation( options?: Omit< @@ -195,14 +197,15 @@ type ProcedureHookGroup = { }; }; -export type ProcedureHooks = Schema['procedures'] extends Record - ? { - /** - * Custom procedures. - */ - $procs: ProcedureHookGroup; - } - : Record; +export type ProcedureHooks> = + Schema['procedures'] extends Record + ? { + /** + * Custom procedures. + */ + $procs: ProcedureHookGroup; + } + : Record; // Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems // to significantly slow down tsc performance ... @@ -210,56 +213,57 @@ export type ModelQueryHooks< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = TrimDelegateModelOperations< +> = TrimSlicedOperations< Schema, Model, + Options, { - useFindUnique>( - args: SelectSubset>, + useFindUnique>( + args: SelectSubset>, options?: ModelQueryOptions | null>, ): ModelQueryResult | null>; - useSuspenseFindUnique>( - args: SelectSubset>, + useSuspenseFindUnique>( + args: SelectSubset>, options?: ModelSuspenseQueryOptions | null>, ): ModelSuspenseQueryResult | null>; - useFindFirst>( - args?: SelectSubset>, + useFindFirst>( + args?: SelectSubset>, options?: ModelQueryOptions | null>, ): ModelQueryResult | null>; - useSuspenseFindFirst>( - args?: SelectSubset>, + useSuspenseFindFirst>( + args?: SelectSubset>, options?: ModelSuspenseQueryOptions | null>, ): ModelSuspenseQueryResult | null>; - useExists>( - args?: Subset>, + useExists>( + args?: Subset>, options?: ModelQueryOptions, ): ModelQueryResult; - useFindMany>( - args?: SelectSubset>, + useFindMany>( + args?: SelectSubset>, options?: ModelQueryOptions[]>, ): ModelQueryResult[]>; - useSuspenseFindMany>( - args?: SelectSubset>, + useSuspenseFindMany>( + args?: SelectSubset>, options?: ModelSuspenseQueryOptions[]>, ): ModelSuspenseQueryResult[]>; - useInfiniteFindMany>( - args?: SelectSubset>, + useInfiniteFindMany>( + args?: SelectSubset>, options?: ModelInfiniteQueryOptions[]>, ): ModelInfiniteQueryResult[]>>; - useSuspenseInfiniteFindMany>( - args?: SelectSubset>, + useSuspenseInfiniteFindMany>( + args?: SelectSubset>, options?: ModelSuspenseInfiniteQueryOptions[]>, ): ModelSuspenseInfiniteQueryResult[]>>; - useCreate>( + useCreate>( options?: ModelMutationOptions, T>, ): ModelMutationModelResult; @@ -267,61 +271,61 @@ export type ModelQueryHooks< options?: ModelMutationOptions, ): ModelMutationResult; - useCreateManyAndReturn>( + useCreateManyAndReturn>( options?: ModelMutationOptions[], T>, ): ModelMutationModelResult; - useUpdate>( + useUpdate>( options?: ModelMutationOptions, T>, ): ModelMutationModelResult; - useUpdateMany>( + useUpdateMany>( options?: ModelMutationOptions, ): ModelMutationResult; - useUpdateManyAndReturn>( + useUpdateManyAndReturn>( options?: ModelMutationOptions[], T>, ): ModelMutationModelResult; - useUpsert>( + useUpsert>( options?: ModelMutationOptions, T>, ): ModelMutationModelResult; - useDelete>( + useDelete>( options?: ModelMutationOptions, T>, ): ModelMutationModelResult; - useDeleteMany>( + useDeleteMany>( options?: ModelMutationOptions, ): ModelMutationResult; - useCount>( - args?: Subset>, + useCount>( + args?: Subset>, options?: ModelQueryOptions>, ): ModelQueryResult>; - useSuspenseCount>( - args?: Subset>, + useSuspenseCount>( + args?: Subset>, options?: ModelSuspenseQueryOptions>, ): ModelSuspenseQueryResult>; - useAggregate>( - args: Subset>, + useAggregate>( + args: Subset>, options?: ModelQueryOptions>, ): ModelQueryResult>; - useSuspenseAggregate>( - args: Subset>, + useSuspenseAggregate>( + args: Subset>, options?: ModelSuspenseQueryOptions>, ): ModelSuspenseQueryResult>; - useGroupBy>( - args: Subset>, + useGroupBy>( + args: Subset>, options?: ModelQueryOptions>, ): ModelQueryResult>; - useSuspenseGroupBy>( - args: Subset>, + useSuspenseGroupBy>( + args: Subset>, options?: ModelSuspenseQueryOptions>, ): ModelSuspenseQueryResult>; } diff --git a/packages/clients/tanstack-query/src/svelte/index.svelte.ts b/packages/clients/tanstack-query/src/svelte/index.svelte.ts index ed743baf2..45af47cb3 100644 --- a/packages/clients/tanstack-query/src/svelte/index.svelte.ts +++ b/packages/clients/tanstack-query/src/svelte/index.svelte.ts @@ -40,6 +40,8 @@ import type { FindUniqueArgs, GetProcedure, GetProcedureNames, + GetSlicedModels, + GetSlicedProcedures, GroupByArgs, GroupByResult, ProcedureEnvelope, @@ -63,7 +65,7 @@ import type { ExtraQueryOptions, ProcedureReturn, QueryContext, - TrimDelegateModelOperations, + TrimSlicedOperations, WithOptimistic, } from '../common/types.js'; export type { FetchFn } from '@zenstackhq/client-helpers/fetch'; @@ -145,11 +147,11 @@ export type ModelMutationModelResult< }; export type ClientHooks = QueryOptions> = { - [Model in GetModels as `${Uncapitalize}`]: ModelQueryHooks; -} & ProcedureHooks; + [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks; +} & ProcedureHooks; -type ProcedureHookGroup = { - [Name in GetProcedureNames]: GetProcedure extends { mutation: true } +type ProcedureHookGroup> = { + [Name in GetSlicedProcedures]: GetProcedure extends { mutation: true } ? { useMutation( options?: Omit< @@ -182,14 +184,15 @@ type ProcedureHookGroup = { }; }; -export type ProcedureHooks = Schema['procedures'] extends Record - ? { - /** - * Custom procedures. - */ - $procs: ProcedureHookGroup; - } - : Record; +export type ProcedureHooks> = + Schema['procedures'] extends Record + ? { + /** + * Custom procedures. + */ + $procs: ProcedureHookGroup; + } + : Record; // Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems // to significantly slow down tsc performance ... @@ -197,36 +200,37 @@ export type ModelQueryHooks< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = TrimDelegateModelOperations< +> = TrimSlicedOperations< Schema, Model, + Options, { - useFindUnique>( - args: Accessor>>, + useFindUnique>( + args: Accessor>>, options?: Accessor | null>>, ): ModelQueryResult | null>; - useFindFirst>( - args?: Accessor>>, + useFindFirst>( + args?: Accessor>>, options?: Accessor | null>>, ): ModelQueryResult | null>; - useExists>( - args?: Accessor>>, + useExists>( + args?: Accessor>>, options?: Accessor>, ): ModelQueryResult; - useFindMany>( - args?: Accessor>>, + useFindMany>( + args?: Accessor>>, options?: Accessor[]>>, ): ModelQueryResult[]>; - useInfiniteFindMany>( - args?: Accessor>>, + useInfiniteFindMany>( + args?: Accessor>>, options?: Accessor[]>>, ): ModelInfiniteQueryResult[]>>; - useCreate>( + useCreate>( options?: Accessor, T>>, ): ModelMutationModelResult; @@ -234,44 +238,44 @@ export type ModelQueryHooks< options?: Accessor>, ): ModelMutationResult; - useCreateManyAndReturn>( + useCreateManyAndReturn>( options?: Accessor[], T>>, ): ModelMutationModelResult; - useUpdate>( + useUpdate>( options?: Accessor, T>>, ): ModelMutationModelResult; - useUpdateMany>( + useUpdateMany>( options?: Accessor>, ): ModelMutationResult; - useUpdateManyAndReturn>( + useUpdateManyAndReturn>( options?: Accessor[], T>>, ): ModelMutationModelResult; - useUpsert>( + useUpsert>( options?: Accessor, T>>, ): ModelMutationModelResult; - useDelete>( + useDelete>( options?: Accessor, T>>, ): ModelMutationModelResult; - useDeleteMany>( + useDeleteMany>( options?: Accessor>, ): ModelMutationResult; - useCount>( - args?: Accessor>>, + useCount>( + args?: Accessor>>, options?: Accessor>>, ): ModelQueryResult>; - useAggregate>( - args: Accessor>>, + useAggregate>( + args: Accessor>>, options?: Accessor>>, ): ModelQueryResult>; - useGroupBy>( - args: Accessor>>, + useGroupBy>( + args: Accessor>>, options?: Accessor>>, ): ModelQueryResult>; } diff --git a/packages/clients/tanstack-query/src/vue.ts b/packages/clients/tanstack-query/src/vue.ts index ab8821a0f..b45a28333 100644 --- a/packages/clients/tanstack-query/src/vue.ts +++ b/packages/clients/tanstack-query/src/vue.ts @@ -38,6 +38,8 @@ import type { FindUniqueArgs, GetProcedure, GetProcedureNames, + GetSlicedModels, + GetSlicedProcedures, GroupByArgs, GroupByResult, ProcedureEnvelope, @@ -61,7 +63,7 @@ import type { ExtraQueryOptions, ProcedureReturn, QueryContext, - TrimDelegateModelOperations, + TrimSlicedOperations, WithOptimistic, } from './common/types.js'; export type { FetchFn } from '@zenstackhq/client-helpers/fetch'; @@ -138,11 +140,11 @@ export type ModelMutationModelResult< }; export type ClientHooks = QueryOptions> = { - [Model in GetModels as `${Uncapitalize}`]: ModelQueryHooks; -} & ProcedureHooks; + [Model in GetSlicedModels as `${Uncapitalize}`]: ModelQueryHooks; +} & ProcedureHooks; -type ProcedureHookGroup = { - [Name in GetProcedureNames]: GetProcedure extends { mutation: true } +type ProcedureHookGroup> = { + [Name in GetSlicedProcedures]: GetProcedure extends { mutation: true } ? { useMutation( options?: MaybeRefOrGetter< @@ -184,14 +186,15 @@ type ProcedureHookGroup = { }; }; -export type ProcedureHooks = Schema['procedures'] extends Record - ? { - /** - * Custom procedures. - */ - $procs: ProcedureHookGroup; - } - : Record; +export type ProcedureHooks> = + Schema['procedures'] extends Record + ? { + /** + * Custom procedures. + */ + $procs: ProcedureHookGroup; + } + : Record; // Note that we can potentially use TypeScript's mapped type to directly map from ORM contract, but that seems // to significantly slow down tsc performance ... @@ -199,36 +202,37 @@ export type ModelQueryHooks< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = TrimDelegateModelOperations< +> = TrimSlicedOperations< Schema, Model, + Options, { - useFindUnique>( - args: MaybeRefOrGetter>>, + useFindUnique>( + args: MaybeRefOrGetter>>, options?: MaybeRefOrGetter | null>>, ): ModelQueryResult | null>; - useFindFirst>( - args?: MaybeRefOrGetter>>, + useFindFirst>( + args?: MaybeRefOrGetter>>, options?: MaybeRefOrGetter | null>>, ): ModelQueryResult | null>; - useExists>( - args?: MaybeRefOrGetter>>, + useExists>( + args?: MaybeRefOrGetter>>, options?: MaybeRefOrGetter>, ): ModelQueryResult; - useFindMany>( - args?: MaybeRefOrGetter>>, + useFindMany>( + args?: MaybeRefOrGetter>>, options?: MaybeRefOrGetter[]>>, ): ModelQueryResult[]>; - useInfiniteFindMany>( - args?: MaybeRefOrGetter>>, + useInfiniteFindMany>( + args?: MaybeRefOrGetter>>, options?: MaybeRefOrGetter[]>>, ): ModelInfiniteQueryResult[]>>; - useCreate>( + useCreate>( options?: MaybeRefOrGetter, T>>, ): ModelMutationModelResult; @@ -236,46 +240,46 @@ export type ModelQueryHooks< options?: MaybeRefOrGetter>, ): ModelMutationResult; - useCreateManyAndReturn>( + useCreateManyAndReturn>( options?: MaybeRefOrGetter[], T>>, ): ModelMutationModelResult; - useUpdate>( + useUpdate>( options?: MaybeRefOrGetter, T>>, ): ModelMutationModelResult; - useUpdateMany>( + useUpdateMany>( options?: MaybeRefOrGetter>, ): ModelMutationResult; - useUpdateManyAndReturn>( + useUpdateManyAndReturn>( options?: MaybeRefOrGetter[], T>>, ): ModelMutationModelResult; - useUpsert>( + useUpsert>( options?: MaybeRefOrGetter, T>>, ): ModelMutationModelResult; - useDelete>( + useDelete>( options?: MaybeRefOrGetter, T>>, ): ModelMutationModelResult; - useDeleteMany>( + useDeleteMany>( options?: MaybeRefOrGetter>, ): ModelMutationResult; - useCount>( - args?: MaybeRefOrGetter>>, + useCount>( + args?: MaybeRefOrGetter>>, options?: MaybeRefOrGetter>>, ): ModelQueryResult>; - useAggregate>( - args: MaybeRefOrGetter>>, + useAggregate>( + args: MaybeRefOrGetter>>, options?: MaybeRefOrGetter>>, ): ModelQueryResult>; - useGroupBy>( - args: MaybeRefOrGetter>>, + useGroupBy>( + args: MaybeRefOrGetter>>, options?: MaybeRefOrGetter>>, ): ModelQueryResult>; } diff --git a/packages/clients/tanstack-query/test/react-sliced-client.test-d.ts b/packages/clients/tanstack-query/test/react-sliced-client.test-d.ts new file mode 100644 index 000000000..8b31551d7 --- /dev/null +++ b/packages/clients/tanstack-query/test/react-sliced-client.test-d.ts @@ -0,0 +1,93 @@ +import { ZenStackClient, type GetQueryOptions } from '@zenstackhq/orm'; +import { describe, expectTypeOf, it } from 'vitest'; +import { useClientQueries } from '../src/react'; +import { schema } from './schemas/basic/schema-lite'; +import { schema as procSchema } from './schemas/procedures/schema-lite'; + +describe('React client sliced client test', () => { + const _db = new ZenStackClient(schema, { + dialect: {} as any, + slicing: { + includedModels: ['User', 'Post'], + models: { + user: { + includedOperations: ['findUnique', 'findMany', 'update'], + excludedOperations: ['update'], + }, + }, + }, + omit: {}, + }); + + it('works with sliced models', () => { + const client = useClientQueries>(schema); + + expectTypeOf(client).toHaveProperty('user'); + expectTypeOf(client).toHaveProperty('post'); + expectTypeOf(client).not.toHaveProperty('category'); + }); + + it('works with sliced operations', () => { + const client = useClientQueries< + typeof schema, + { + slicing: { + models: { + user: { + includedOperations: ['findUnique', 'findMany', 'update']; + }; + }; + }; + } + >(schema); + + expectTypeOf(client.user).toHaveProperty('useFindUnique'); + expectTypeOf(client.user).toHaveProperty('useFindMany'); + expectTypeOf(client.user).toHaveProperty('useUpdate'); + expectTypeOf(client.user).not.toHaveProperty('useFindFirst'); + }); + + it('works with sliced filters', () => { + const client = useClientQueries< + typeof schema, + { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality']; + }; + }; + }; + }; + }; + } + >(schema); + + // Equality filter should be allowed + client.user.useFindMany({ + where: { name: { equals: 'test' } }, + }); + + // 'Like' filter kind should not be available + // @ts-expect-error - 'contains' is not allowed when only 'Equality' filter kind is included + client.user.useFindMany({ where: { name: { contains: 'test' } } }); + }); + + it('works with sliced procedures', () => { + const client = useClientQueries< + typeof procSchema, + { + slicing: { + includedProcedures: ['greet', 'sum']; + excludedProcedures: ['sum']; + }; + } + >(procSchema); + + expectTypeOf(client.$procs).toHaveProperty('greet'); + expectTypeOf(client.$procs).not.toHaveProperty('sum'); + expectTypeOf(client.$procs).not.toHaveProperty('greetMany'); + }); +}); diff --git a/packages/clients/tanstack-query/test/react-typing-test.ts b/packages/clients/tanstack-query/test/react-typing-test.ts deleted file mode 100644 index b99a31217..000000000 --- a/packages/clients/tanstack-query/test/react-typing-test.ts +++ /dev/null @@ -1,143 +0,0 @@ -import { useClientQueries } from '../src/react'; -import { schema } from './schemas/basic/schema-lite'; -import { schema as proceduresSchema } from './schemas/procedures/schema-lite'; - -const client = useClientQueries(schema); -const proceduresClient = useClientQueries(proceduresSchema); - -// @ts-expect-error missing args -client.user.useFindUnique(); - -check(client.user.useFindUnique({ where: { id: '1' } }).data?.email); -check(client.user.useFindUnique({ where: { id: '1' } }).queryKey); -check(client.user.useFindUnique({ where: { id: '1' } }, { optimisticUpdate: true, enabled: false })); - -// @ts-expect-error unselected field -check(client.user.useFindUnique({ select: { email: true } }).data.name); - -check(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } }).data?.posts[0]?.title); - -check(client.user.useFindFirst().data?.email); -check(client.user.useFindFirst().data?.$optimistic); - -check(client.user.useExists().data); -check(client.user.useExists({ where: { id: '1' } }).data); - -check(client.user.useFindMany().data?.[0]?.email); -check(client.user.useFindMany().data?.[0]?.$optimistic); - -check(client.user.useInfiniteFindMany().data?.pages[0]?.[0]?.email); -check( - client.user.useInfiniteFindMany( - {}, - { - getNextPageParam: () => ({ id: '2' }), - }, - ).data?.pages[1]?.[0]?.email, -); -// @ts-expect-error -check(client.user.useInfiniteFindMany().data?.pages[0]?.[0]?.$optimistic); - -check(client.user.useSuspenseFindMany().data[0]?.email); -check(client.user.useSuspenseInfiniteFindMany().data.pages[0]?.[0]?.email); -check(client.user.useCount().data?.toFixed(2)); -check(client.user.useCount({ select: { email: true } }).data?.email.toFixed(2)); - -check(client.user.useAggregate({ _max: { email: true } }).data?._max.email); - -check(client.user.useGroupBy({ by: ['email'], _max: { name: true } }).data?.[0]?._max.name); - -// @ts-expect-error missing args -client.user.useCreate().mutate(); -client.user.useCreate().mutate({ data: { email: 'test@example.com' } }); -client.user - .useCreate({ optimisticUpdate: true, invalidateQueries: false, retry: 3 }) - .mutate({ data: { email: 'test@example.com' } }); - -client.user - .useCreate() - .mutateAsync({ data: { email: 'test@example.com' }, include: { posts: true } }) - .then((d) => check(d.posts[0]?.title)); - -client.user - .useCreateMany() - .mutateAsync({ - data: [{ email: 'test@example.com' }, { email: 'test2@example.com' }], - skipDuplicates: true, - }) - .then((d) => d.count); - -client.user - .useCreateManyAndReturn() - .mutateAsync({ - data: [{ email: 'test@example.com' }], - }) - .then((d) => check(d[0]?.name)); - -client.user - .useCreateManyAndReturn() - .mutateAsync({ - data: [{ email: 'test@example.com' }], - select: { email: true }, - }) - // @ts-expect-error unselected field - .then((d) => check(d[0].name)); - -client.user.useUpdate().mutate( - { data: { email: 'updated@example.com' }, where: { id: '1' } }, - { - onSuccess: (d) => { - check(d.email); - }, - }, -); - -client.user.useUpdateMany().mutate({ data: { email: 'updated@example.com' } }); - -client.user - .useUpdateManyAndReturn() - .mutateAsync({ data: { email: 'updated@example.com' } }) - .then((d) => check(d[0]?.email)); - -client.user - .useUpsert() - .mutate({ where: { id: '1' }, create: { email: 'new@example.com' }, update: { email: 'updated@example.com' } }); - -client.user.useDelete().mutate({ where: { id: '1' }, include: { posts: true } }); - -client.user.useDeleteMany().mutate({ where: { email: 'test@example.com' } }); - -function check(_value: unknown) { - // noop -} - -// @ts-expect-error delegate model -client.foo.useCreate(); - -client.foo.useUpdate(); -client.bar.useCreate(); - -// procedures (query) -check(proceduresClient.$procs.greet.useQuery().data?.toUpperCase()); -check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }).data?.toUpperCase()); -check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }, { enabled: true }).queryKey); -// @ts-expect-error wrong arg shape -proceduresClient.$procs.greet.useQuery({ args: { hello: 'world' } }); - -// Infinite queries for procedures are currently disabled, will add back later if needed -// check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).data?.pages[0]?.[0]?.toUpperCase()); -// check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).queryKey); - -// @ts-expect-error missing args -proceduresClient.$procs.greetMany.useQuery(); -// @ts-expect-error greet is not a mutation procedure -proceduresClient.$procs.greet.useMutation(); - -// procedures (mutation) -proceduresClient.$procs.sum.useMutation().mutate({ args: { a: 1, b: 2 } }); -// @ts-expect-error wrong arg shape for multi-param procedure -proceduresClient.$procs.sum.useMutation().mutate([1, 2]); -proceduresClient.$procs.sum - .useMutation() - .mutateAsync({ args: { a: 1, b: 2 } }) - .then((d) => check(d.toFixed(2))); diff --git a/packages/clients/tanstack-query/test/react-typing.test-d.ts b/packages/clients/tanstack-query/test/react-typing.test-d.ts new file mode 100644 index 000000000..3dbcd8446 --- /dev/null +++ b/packages/clients/tanstack-query/test/react-typing.test-d.ts @@ -0,0 +1,153 @@ +import { describe, it } from 'vitest'; +import { useClientQueries } from '../src/react'; +import { schema } from './schemas/basic/schema-lite'; +import { schema as proceduresSchema } from './schemas/procedures/schema-lite'; + +describe('React client typing test', () => { + it('types model queries correctly', () => { + const client = useClientQueries(schema); + + // @ts-expect-error missing args + client.user.useFindUnique(); + + check(client.user.useFindUnique({ where: { id: '1' } }).data?.email); + check(client.user.useFindUnique({ where: { id: '1' } }).queryKey); + check(client.user.useFindUnique({ where: { id: '1' } }, { optimisticUpdate: true, enabled: false })); + + // @ts-expect-error unselected field + check(client.user.useFindUnique({ select: { email: true } }).data.name); + + check(client.user.useFindUnique({ where: { id: '1' }, include: { posts: true } }).data?.posts[0]?.title); + + check(client.user.useFindFirst().data?.email); + check(client.user.useFindFirst().data?.$optimistic); + + check(client.user.useExists().data); + check(client.user.useExists({ where: { id: '1' } }).data); + + check(client.user.useFindMany().data?.[0]?.email); + check(client.user.useFindMany().data?.[0]?.$optimistic); + + check(client.user.useInfiniteFindMany().data?.pages[0]?.[0]?.email); + check( + client.user.useInfiniteFindMany( + {}, + { + getNextPageParam: () => ({ id: '2' }), + }, + ).data?.pages[1]?.[0]?.email, + ); + // @ts-expect-error + check(client.user.useInfiniteFindMany().data?.pages[0]?.[0]?.$optimistic); + + check(client.user.useSuspenseFindMany().data[0]?.email); + check(client.user.useSuspenseInfiniteFindMany().data.pages[0]?.[0]?.email); + check(client.user.useCount().data?.toFixed(2)); + check(client.user.useCount({ select: { email: true } }).data?.email.toFixed(2)); + + check(client.user.useAggregate({ _max: { email: true } }).data?._max.email); + + check(client.user.useGroupBy({ by: ['email'], _max: { name: true } }).data?.[0]?._max.name); + + // @ts-expect-error missing args + client.user.useCreate().mutate(); + client.user.useCreate().mutate({ data: { email: 'test@example.com' } }); + client.user + .useCreate({ optimisticUpdate: true, invalidateQueries: false, retry: 3 }) + .mutate({ data: { email: 'test@example.com' } }); + + client.user + .useCreate() + .mutateAsync({ data: { email: 'test@example.com' }, include: { posts: true } }) + .then((d) => check(d.posts[0]?.title)); + + client.user + .useCreateMany() + .mutateAsync({ + data: [{ email: 'test@example.com' }, { email: 'test2@example.com' }], + skipDuplicates: true, + }) + .then((d) => d.count); + + client.user + .useCreateManyAndReturn() + .mutateAsync({ + data: [{ email: 'test@example.com' }], + }) + .then((d) => check(d[0]?.name)); + + client.user + .useCreateManyAndReturn() + .mutateAsync({ + data: [{ email: 'test@example.com' }], + select: { email: true }, + }) + // @ts-expect-error unselected field + .then((d) => check(d[0].name)); + + client.user.useUpdate().mutate( + { data: { email: 'updated@example.com' }, where: { id: '1' } }, + { + onSuccess: (d) => { + check(d.email); + }, + }, + ); + + client.user.useUpdateMany().mutate({ data: { email: 'updated@example.com' } }); + + client.user + .useUpdateManyAndReturn() + .mutateAsync({ data: { email: 'updated@example.com' } }) + .then((d) => check(d[0]?.email)); + + client.user.useUpsert().mutate({ + where: { id: '1' }, + create: { email: 'new@example.com' }, + update: { email: 'updated@example.com' }, + }); + + client.user.useDelete().mutate({ where: { id: '1' }, include: { posts: true } }); + + client.user.useDeleteMany().mutate({ where: { email: 'test@example.com' } }); + + // @ts-expect-error delegate model + client.foo.useCreate(); + + client.foo.useUpdate(); + client.bar.useCreate(); + }); + + it('types procedure queries correctly', () => { + const proceduresClient = useClientQueries(proceduresSchema); + + // procedures (query) + check(proceduresClient.$procs.greet.useQuery().data?.toUpperCase()); + check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }).data?.toUpperCase()); + check(proceduresClient.$procs.greet.useQuery({ args: { name: 'bob' } }, { enabled: true }).queryKey); + // @ts-expect-error wrong arg shape + proceduresClient.$procs.greet.useQuery({ args: { hello: 'world' } }); + + // Infinite queries for procedures are currently disabled, will add back later if needed + // check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).data?.pages[0]?.[0]?.toUpperCase()); + // check(proceduresClient.$procs.greetMany.useInfiniteQuery({ args: { name: 'bob' } }).queryKey); + + // @ts-expect-error missing args + proceduresClient.$procs.greetMany.useQuery(); + // @ts-expect-error greet is not a mutation procedure + proceduresClient.$procs.greet.useMutation(); + + // procedures (mutation) + proceduresClient.$procs.sum.useMutation().mutate({ args: { a: 1, b: 2 } }); + // @ts-expect-error wrong arg shape for multi-param procedure + proceduresClient.$procs.sum.useMutation().mutate([1, 2]); + proceduresClient.$procs.sum + .useMutation() + .mutateAsync({ args: { a: 1, b: 2 } }) + .then((d) => check(d.toFixed(2))); + }); +}); + +function check(_value: unknown) { + // noop +} diff --git a/packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts b/packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts new file mode 100644 index 000000000..5b83f6ff4 --- /dev/null +++ b/packages/clients/tanstack-query/test/svelte-sliced-client.test-d.ts @@ -0,0 +1,93 @@ +import { ZenStackClient, type GetQueryOptions } from '@zenstackhq/orm'; +import { describe, expectTypeOf, it } from 'vitest'; +import { useClientQueries } from '../src/svelte/index.svelte'; +import { schema } from './schemas/basic/schema-lite'; +import { schema as procSchema } from './schemas/procedures/schema-lite'; + +describe('Svelte client sliced client test', () => { + const _db = new ZenStackClient(schema, { + dialect: {} as any, + slicing: { + includedModels: ['User', 'Post'], + models: { + user: { + includedOperations: ['findUnique', 'findMany', 'update'], + excludedOperations: ['update'], + }, + }, + }, + omit: {}, + }); + + it('works with sliced models', () => { + const client = useClientQueries>(schema); + + expectTypeOf(client).toHaveProperty('user'); + expectTypeOf(client).toHaveProperty('post'); + expectTypeOf(client).not.toHaveProperty('category'); + }); + + it('works with sliced operations', () => { + const client = useClientQueries< + typeof schema, + { + slicing: { + models: { + user: { + includedOperations: ['findUnique', 'findMany', 'update']; + }; + }; + }; + } + >(schema); + + expectTypeOf(client.user).toHaveProperty('useFindUnique'); + expectTypeOf(client.user).toHaveProperty('useFindMany'); + expectTypeOf(client.user).toHaveProperty('useUpdate'); + expectTypeOf(client.user).not.toHaveProperty('useFindFirst'); + }); + + it('works with sliced filters', () => { + const client = useClientQueries< + typeof schema, + { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality']; + }; + }; + }; + }; + }; + } + >(schema); + + // Equality filter should be allowed + client.user.useFindMany(() => ({ + where: { name: { equals: 'test' } }, + })); + + // 'Like' filter kind should not be available + // @ts-expect-error - 'contains' is not allowed when only 'Equality' filter kind is included + client.user.useFindMany(() => ({ where: { name: { contains: 'test' } } })); + }); + + it('works with sliced procedures', () => { + const client = useClientQueries< + typeof procSchema, + { + slicing: { + includedProcedures: ['greet', 'sum']; + excludedProcedures: ['sum']; + }; + } + >(procSchema); + + expectTypeOf(client.$procs).toHaveProperty('greet'); + expectTypeOf(client.$procs).not.toHaveProperty('sum'); + expectTypeOf(client.$procs).not.toHaveProperty('greetMany'); + }); +}); diff --git a/packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts b/packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts new file mode 100644 index 000000000..5fdd11d6a --- /dev/null +++ b/packages/clients/tanstack-query/test/vue-sliced-client.test-d.ts @@ -0,0 +1,93 @@ +import { ZenStackClient, type GetQueryOptions } from '@zenstackhq/orm'; +import { describe, expectTypeOf, it } from 'vitest'; +import { useClientQueries } from '../src/vue'; +import { schema } from './schemas/basic/schema-lite'; +import { schema as procSchema } from './schemas/procedures/schema-lite'; + +describe('Vue client sliced client test', () => { + const _db = new ZenStackClient(schema, { + dialect: {} as any, + slicing: { + includedModels: ['User', 'Post'], + models: { + user: { + includedOperations: ['findUnique', 'findMany', 'update'], + excludedOperations: ['update'], + }, + }, + }, + omit: {}, + }); + + it('works with sliced models', () => { + const client = useClientQueries>(schema); + + expectTypeOf(client).toHaveProperty('user'); + expectTypeOf(client).toHaveProperty('post'); + expectTypeOf(client).not.toHaveProperty('category'); + }); + + it('works with sliced operations', () => { + const client = useClientQueries< + typeof schema, + { + slicing: { + models: { + user: { + includedOperations: ['findUnique', 'findMany', 'update']; + }; + }; + }; + } + >(schema); + + expectTypeOf(client.user).toHaveProperty('useFindUnique'); + expectTypeOf(client.user).toHaveProperty('useFindMany'); + expectTypeOf(client.user).toHaveProperty('useUpdate'); + expectTypeOf(client.user).not.toHaveProperty('useFindFirst'); + }); + + it('works with sliced filters', () => { + const client = useClientQueries< + typeof schema, + { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality']; + }; + }; + }; + }; + }; + } + >(schema); + + // Equality filter should be allowed + client.user.useFindMany({ + where: { name: { equals: 'test' } }, + }); + + // 'Like' filter kind should not be available + // @ts-expect-error - 'contains' is not allowed when only 'Equality' filter kind is included + client.user.useFindMany({ where: { name: { contains: 'test' } } }); + }); + + it('works with sliced procedures', () => { + const client = useClientQueries< + typeof procSchema, + { + slicing: { + includedProcedures: ['greet', 'sum']; + excludedProcedures: ['sum']; + }; + } + >(procSchema); + + expectTypeOf(client.$procs).toHaveProperty('greet'); + expectTypeOf(client.$procs).not.toHaveProperty('sum'); + expectTypeOf(client.$procs).not.toHaveProperty('greetMany'); + }); +}); diff --git a/packages/clients/tanstack-query/vitest.config.ts b/packages/clients/tanstack-query/vitest.config.ts index 221fe5800..3e6d3bbbb 100644 --- a/packages/clients/tanstack-query/vitest.config.ts +++ b/packages/clients/tanstack-query/vitest.config.ts @@ -6,6 +6,10 @@ export default mergeConfig( defineConfig({ test: { include: ['test/**/*.test.ts', 'test/**/*.test.tsx'], + typecheck: { + enabled: true, + tsconfig: 'tsconfig.test.json', + }, }, }), ); diff --git a/packages/orm/src/client/client-impl.ts b/packages/orm/src/client/client-impl.ts index acf888f8a..8eec17a13 100644 --- a/packages/orm/src/client/client-impl.ts +++ b/packages/orm/src/client/client-impl.ts @@ -1,4 +1,4 @@ -import { invariant } from '@zenstackhq/common-helpers'; +import { invariant, lowerCaseFirst } from '@zenstackhq/common-helpers'; import type { QueryExecutor } from 'kysely'; import { CompiledQuery, @@ -257,6 +257,10 @@ export class ClientImpl { get $procs() { return Object.keys(this.$schema.procedures ?? {}).reduce((acc, name) => { + // Filter procedures based on slicing configuration + if (!isProcedureIncluded(this.$options, name)) { + return acc; + } acc[name] = (input?: unknown) => this.handleProc(name, input); return acc; }, {} as any); @@ -462,6 +466,10 @@ function createClientProxy(client: ClientImpl): ClientImpl { if (typeof prop === 'string') { const model = Object.keys(client.$schema.models).find((m) => m.toLowerCase() === prop.toLowerCase()); if (model) { + // Check if model is allowed by slicing configuration + if (!isModelIncluded(client.$options, model)) { + return undefined; + } return createModelCrudHandler(client as any, model, client.inputValidator, resultProcessor); } } @@ -471,6 +479,64 @@ function createClientProxy(client: ClientImpl): ClientImpl { }) as unknown as ClientImpl; } +/** + * Checks if a model should be included based on slicing configuration. + */ +function isModelIncluded(options: ClientOptions, model: string): boolean { + const slicing = options.slicing; + if (!slicing) { + // No slicing config, include all models + return true; + } + + const { includedModels, excludedModels } = slicing; + + // If includedModels is specified (even if empty), only include those models + if (includedModels !== undefined) { + if (!includedModels.includes(model as any)) { + return false; + } + } + + // Then check if model is excluded + if (excludedModels && excludedModels.length > 0) { + if (excludedModels.includes(model as any)) { + return false; + } + } + + return true; +} + +/** + * Checks if a procedure should be included based on slicing configuration. + */ +function isProcedureIncluded(options: ClientOptions, procedureName: string): boolean { + const slicing = options.slicing; + if (!slicing) { + // No slicing config, include all procedures + return true; + } + + const { includedProcedures, excludedProcedures } = slicing; + + // If includedProcedures is specified (even if empty), only include those procedures + if (includedProcedures !== undefined) { + if (!(includedProcedures as readonly string[]).includes(procedureName)) { + return false; + } + } + + // Then check if procedure is excluded (exclusion takes precedence) + if (excludedProcedures && excludedProcedures.length > 0) { + if ((excludedProcedures as readonly string[]).includes(procedureName)) { + return false; + } + } + + return true; +} + function createModelCrudHandler( client: ClientContract, model: string, @@ -527,7 +593,7 @@ function createModelCrudHandler( }; // type parameters to operation handlers are explicitly specified to improve tsc performance - return { + const operations = { findUnique: (args: unknown) => { return createPromise( 'findUnique', @@ -720,5 +786,36 @@ function createModelCrudHandler( false, ); }, - } as ModelOperations; + }; + + // Filter operations based on slicing configuration + const slicing = client.$options.slicing; + if (slicing?.models) { + const modelSlicing = slicing.models[lowerCaseFirst(model) as any]; + const allSlicing = slicing.models.$all; + + // Determine includedOperations: model-specific takes precedence over $all + const includedOperations = modelSlicing?.includedOperations ?? allSlicing?.includedOperations; + + // Determine excludedOperations: model-specific takes precedence over $all + const excludedOperations = modelSlicing?.excludedOperations ?? allSlicing?.excludedOperations; + + // If includedOperations is specified, remove operations not in the list + if (includedOperations !== undefined) { + for (const key of Object.keys(operations)) { + if (!includedOperations.includes(key as any)) { + delete (operations as any)[key]; + } + } + } + + // Then remove explicitly excluded operations + if (excludedOperations && excludedOperations.length > 0) { + for (const operation of excludedOperations) { + delete (operations as any)[operation]; + } + } + } + + return operations as ModelOperations; } diff --git a/packages/orm/src/client/constants.ts b/packages/orm/src/client/constants.ts index 129cf3490..bf62faff6 100644 --- a/packages/orm/src/client/constants.ts +++ b/packages/orm/src/client/constants.ts @@ -26,5 +26,56 @@ export const LOGICAL_COMBINATORS = ['AND', 'OR', 'NOT'] as const; /** * Aggregation operators. */ -export const AGGREGATE_OPERATORS = ['_count', '_sum', '_avg', '_min', '_max'] as const; -export type AGGREGATE_OPERATORS = (typeof AGGREGATE_OPERATORS)[number]; +export const AggregateOperators = ['_count', '_sum', '_avg', '_min', '_max'] as const; +export type AggregateOperators = (typeof AggregateOperators)[number]; + +/** + * Mapping of filter operators to their corresponding filter kind categories. + */ +export const FILTER_PROPERTY_TO_KIND = { + // Equality operators + equals: 'Equality', + not: 'Equality', + in: 'Equality', + notIn: 'Equality', + + // Range operators + lt: 'Range', + lte: 'Range', + gt: 'Range', + gte: 'Range', + between: 'Range', + + // Like operators + contains: 'Like', + startsWith: 'Like', + endsWith: 'Like', + mode: 'Like', + + // Relation operators + is: 'Relation', + isNot: 'Relation', + some: 'Relation', + every: 'Relation', + none: 'Relation', + + // Json operators + path: 'Json', + string_contains: 'Json', + string_starts_with: 'Json', + string_ends_with: 'Json', + array_contains: 'Json', + array_starts_with: 'Json', + array_ends_with: 'Json', + + // List operators + has: 'List', + hasEvery: 'List', + hasSome: 'List', + isEmpty: 'List', +} as const; + +/** + * Mapping of filter operators to their corresponding filter kind categories. + */ +export type FilterPropertyToKind = typeof FILTER_PROPERTY_TO_KIND; diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index 7492c02bf..9b038722f 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -27,7 +27,6 @@ import type { FindFirstArgs, FindManyArgs, FindUniqueArgs, - GetProcedureNames, GroupByArgs, GroupByResult, ProcedureFunc, @@ -47,10 +46,11 @@ import type { CoreReadOperations, CoreUpdateOperations, } from './crud/operations/base'; -import type { ClientOptions, QueryOptions, ToQueryOptions } from './options'; +import type { ClientOptions, QueryOptions } from './options'; import type { ExtClientMembersBase, ExtQueryArgsBase, RuntimePlugin } from './plugin'; import type { ZenStackPromise } from './promise'; import type { ToKysely } from './query-builder'; +import type { GetSlicedModels, GetSlicedOperations, GetSlicedProcedures } from './type-utils'; type TransactionUnsupportedMethods = (typeof TRANSACTION_UNSUPPORTED_METHODS)[number]; @@ -238,13 +238,8 @@ export type ClientContract< */ $pushSchema(): Promise; } & { - [Key in GetModels as Uncapitalize]: ModelOperations< - Schema, - Key, - ToQueryOptions, - ExtQueryArgs - >; -} & ProcedureOperations & + [Key in GetSlicedModels as Uncapitalize]: ModelOperations; +} & ProcedureOperations & ExtClientMembers; /** @@ -257,14 +252,17 @@ export type TransactionClientContract< ExtClientMembers extends ExtClientMembersBase, > = Omit, TransactionUnsupportedMethods>; -export type ProcedureOperations = +export type ProcedureOperations< + Schema extends SchemaDef, + Options extends ClientOptions = ClientOptions, +> = Schema['procedures'] extends Record ? { /** * Custom procedures. */ $procs: { - [Key in GetProcedureNames]: ProcedureFunc; + [Key in GetSlicedProcedures]: ProcedureFunc; }; } : {}; @@ -299,7 +297,21 @@ export const CRUD = ['create', 'read', 'update', 'delete'] as const; */ export const CRUD_EXT = [...CRUD, 'post-update'] as const; -//#region Model operations +// #region Model operations + +type SliceOperations< + T extends Record, + Schema extends SchemaDef, + Model extends GetModels, + Options extends ClientOptions, +> = Omit< + { + // keep only operations included by slicing options + [Key in keyof T as Key extends GetSlicedOperations ? Key : never]: T[Key]; + }, + // exclude operations not applicable to delegate models + IsDelegateModel extends true ? OperationsIneligibleForDelegateModels : never +>; export type AllModelOperations< Schema extends SchemaDef, @@ -330,12 +342,13 @@ export type AllModelOperations< * ``` */ createManyAndReturn< - T extends CreateManyAndReturnArgs & + T extends CreateManyAndReturnArgs & ExtractExtQueryArgs, >( args?: SelectSubset< T, - CreateManyAndReturnArgs & ExtractExtQueryArgs + CreateManyAndReturnArgs & + ExtractExtQueryArgs >, ): ZenStackPromise[]>; @@ -362,12 +375,13 @@ export type AllModelOperations< * ``` */ updateManyAndReturn< - T extends UpdateManyAndReturnArgs & + T extends UpdateManyAndReturnArgs & ExtractExtQueryArgs, >( args: Subset< T, - UpdateManyAndReturnArgs & ExtractExtQueryArgs + UpdateManyAndReturnArgs & + ExtractExtQueryArgs >, ): ZenStackPromise[]>; }); @@ -459,8 +473,8 @@ type CommonModelOperations< * }); // result: `{ _count: { posts: number } }` * ``` */ - findMany & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + findMany & ExtractExtQueryArgs>( + args?: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise[]>; /** @@ -469,8 +483,8 @@ type CommonModelOperations< * @returns a single entity or null if not found * @see {@link findMany} */ - findUnique & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + findUnique & ExtractExtQueryArgs>( + args: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise | null>; /** @@ -479,8 +493,10 @@ type CommonModelOperations< * @returns a single entity * @see {@link findMany} */ - findUniqueOrThrow & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + findUniqueOrThrow< + T extends FindUniqueArgs & ExtractExtQueryArgs, + >( + args: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise>; /** @@ -489,8 +505,8 @@ type CommonModelOperations< * @returns a single entity or null if not found * @see {@link findMany} */ - findFirst & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + findFirst & ExtractExtQueryArgs>( + args?: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise | null>; /** @@ -499,8 +515,8 @@ type CommonModelOperations< * @returns a single entity * @see {@link findMany} */ - findFirstOrThrow & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + findFirstOrThrow & ExtractExtQueryArgs>( + args?: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise>; /** @@ -555,8 +571,8 @@ type CommonModelOperations< * }); * ``` */ - create & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + create & ExtractExtQueryArgs>( + args: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise>; /** @@ -705,8 +721,8 @@ type CommonModelOperations< * }); * ``` */ - update & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + update & ExtractExtQueryArgs>( + args: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise>; /** @@ -729,8 +745,8 @@ type CommonModelOperations< * limit: 10 * }); */ - updateMany & ExtractExtQueryArgs>( - args: Subset & ExtractExtQueryArgs>, + updateMany & ExtractExtQueryArgs>( + args: Subset & ExtractExtQueryArgs>, ): ZenStackPromise; /** @@ -753,8 +769,8 @@ type CommonModelOperations< * }); * ``` */ - upsert & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + upsert & ExtractExtQueryArgs>( + args: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise>; /** @@ -776,8 +792,8 @@ type CommonModelOperations< * }); // result: `{ id: string; email: string }` * ``` */ - delete & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + delete & ExtractExtQueryArgs>( + args: SelectSubset & ExtractExtQueryArgs>, ): ZenStackPromise>; /** @@ -799,8 +815,8 @@ type CommonModelOperations< * }); * ``` */ - deleteMany & ExtractExtQueryArgs>( - args?: Subset & ExtractExtQueryArgs>, + deleteMany & ExtractExtQueryArgs>( + args?: Subset & ExtractExtQueryArgs>, ): ZenStackPromise; /** @@ -821,8 +837,8 @@ type CommonModelOperations< * select: { _all: true, email: true } * }); // result: `{ _all: number, email: number }` */ - count & ExtractExtQueryArgs>( - args?: Subset & ExtractExtQueryArgs>, + count & ExtractExtQueryArgs>( + args?: Subset & ExtractExtQueryArgs>, ): ZenStackPromise>>; /** @@ -842,8 +858,8 @@ type CommonModelOperations< * _max: { age: true } * }); // result: `{ _count: number, _avg: { age: number }, ... }` */ - aggregate & ExtractExtQueryArgs>( - args: Subset & ExtractExtQueryArgs>, + aggregate & ExtractExtQueryArgs>( + args: Subset & ExtractExtQueryArgs>, ): ZenStackPromise>>; /** @@ -879,8 +895,8 @@ type CommonModelOperations< * having: { country: 'US', age: { _avg: { gte: 18 } } } * }); */ - groupBy & ExtractExtQueryArgs>( - args: Subset & ExtractExtQueryArgs>, + groupBy & ExtractExtQueryArgs>( + args: Subset & ExtractExtQueryArgs>, ): ZenStackPromise>>; /** @@ -900,8 +916,8 @@ type CommonModelOperations< * where: { posts: { some: { published: true } } }, * }); // result: `boolean` */ - exists & ExtractExtQueryArgs>( - args?: Subset & ExtractExtQueryArgs>, + exists & ExtractExtQueryArgs>( + args?: Subset & ExtractExtQueryArgs>, ): ZenStackPromise; }; @@ -910,13 +926,9 @@ export type OperationsIneligibleForDelegateModels = 'create' | 'createMany' | 'c export type ModelOperations< Schema extends SchemaDef, Model extends GetModels, - Options extends QueryOptions = QueryOptions, + Options extends ClientOptions = ClientOptions, ExtQueryArgs = {}, -> = Omit< - AllModelOperations, - // exclude operations not applicable to delegate models - IsDelegateModel extends true ? OperationsIneligibleForDelegateModels : never ->; +> = SliceOperations, Schema, Model, Options>; //#endregion diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 62be0c199..5b050f56a 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -50,8 +50,9 @@ import type { XOR, } from '../utils/type-utils'; import type { ClientContract } from './contract'; -import type { QueryOptions } from './options'; +import type { FilterKind, QueryOptions } from './options'; import type { ToKyselySchema } from './query-builder'; +import type { GetSlicedFilterKindsForField, GetSlicedModels } from './type-utils'; //#region Query results @@ -149,7 +150,7 @@ type ModelSelectResult< never : Key extends '_count' ? // select "_count" - Select[Key] extends SelectCount + Select[Key] extends SelectCount ? Key : never : Key extends keyof Omit @@ -278,6 +279,7 @@ export type BatchResult = { count: number }; export type WhereInput< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions = QueryOptions, ScalarOnly extends boolean = false, WithAggregations extends boolean = false, > = { @@ -285,129 +287,161 @@ export type WhereInput< ? Key extends RelationFields ? never : Key - : Key]?: Key extends RelationFields + : Key]?: FieldFilter; +} & { + $expr?: (eb: ExpressionBuilder, Model>) => OperandExpression; +} & { + AND?: OrArray>; + OR?: WhereInput[]; + NOT?: OrArray>; +}; + +type FieldFilter< + Schema extends SchemaDef, + Model extends GetModels, + Field extends GetModelFields, + Options extends QueryOptions, + WithAggregations extends boolean, + AllowedKinds extends FilterKind = GetSlicedFilterKindsForField, +> = + Field extends RelationFields ? // relation - RelationFilter - : FieldIsArray extends true - ? ArrayFilter> - : // enum - GetModelFieldType extends GetEnums - ? EnumFilter< + RelationFilter + : FieldIsArray extends true + ? // array + ArrayFilter, AllowedKinds> + : GetModelFieldType extends GetEnums + ? // enum + EnumFilter< Schema, - GetModelFieldType, - ModelFieldIsOptional, - WithAggregations + GetModelFieldType, + ModelFieldIsOptional, + WithAggregations, + AllowedKinds > - : GetModelFieldType extends GetTypeDefs - ? TypedJsonFilter< + : GetModelFieldType extends GetTypeDefs + ? // typedef + TypedJsonFilter< Schema, - GetModelFieldType, - FieldIsArray, - ModelFieldIsOptional + GetModelFieldType, + FieldIsArray, + ModelFieldIsOptional, + AllowedKinds > : // primitive PrimitiveFilter< - GetModelFieldType, - ModelFieldIsOptional, - WithAggregations + GetModelFieldType, + ModelFieldIsOptional, + WithAggregations, + AllowedKinds >; -} & { - $expr?: (eb: ExpressionBuilder, Model>) => OperandExpression; -} & { - AND?: OrArray>; - OR?: WhereInput[]; - NOT?: OrArray>; -}; type EnumFilter< Schema extends SchemaDef, T extends GetEnums, Nullable extends boolean, WithAggregations extends boolean, + AllowedKinds extends FilterKind, > = - | NullableIf, Nullable> - | ({ - /** - * Checks for equality with the specified enum value. - */ - equals?: NullableIf, Nullable>; - - /** - * Checks if the enum value is in the specified list of values. - */ - in?: (keyof GetEnum)[]; - - /** - * Checks if the enum value is not in the specified list of values. - */ - notIn?: (keyof GetEnum)[]; - - /** - * Builds a negated filter. - */ - not?: EnumFilter; - } & (WithAggregations extends true + | ('Equality' extends AllowedKinds ? NullableIf, Nullable> : never) + | (('Equality' extends AllowedKinds ? { /** - * Filters against the count of records. + * Checks for equality with the specified enum value. */ - _count?: NumberFilter<'Int', false, false>; + equals?: NullableIf, Nullable>; /** - * Filters against the minimum value. + * Checks if the enum value is in the specified list of values. */ - _min?: EnumFilter; + in?: (keyof GetEnum)[]; /** - * Filters against the maximum value. + * Checks if the enum value is not in the specified list of values. */ - _max?: EnumFilter; + notIn?: (keyof GetEnum)[]; } - : {})); + : {}) & { + /** + * Builds a negated filter. + */ + not?: EnumFilter; + } & (WithAggregations extends true + ? { + /** + * Filters against the count of records. + */ + _count?: NumberFilter<'Int', false, false, AllowedKinds>; -type ArrayFilter = { - /** - * Checks if the array equals the specified array. - */ - equals?: MapScalarType[] | null; + /** + * Filters against the minimum value. + */ + _min?: EnumFilter; - /** - * Checks if the array contains all elements of the specified array. - */ - has?: MapScalarType | null; + /** + * Filters against the maximum value. + */ + _max?: EnumFilter; + } + : {})); - /** - * Checks if the array contains any of the elements of the specified array. - */ - hasEvery?: MapScalarType[]; +type ArrayFilter< + Schema extends SchemaDef, + Type extends string, + AllowedKinds extends FilterKind, +> = ('Equality' extends AllowedKinds + ? { + /** + * Checks if the array equals the specified array. + */ + equals?: MapScalarType[] | null; + } + : {}) & + ('List' extends AllowedKinds + ? { + /** + * Checks if the array contains all elements of the specified array. + */ + has?: MapScalarType | null; - /** - * Checks if the array contains some of the elements of the specified array. - */ - hasSome?: MapScalarType[]; + /** + * Checks if the array contains any of the elements of the specified array. + */ + hasEvery?: MapScalarType[]; - /** - * Checks if the array is empty. - */ - isEmpty?: boolean; -}; + /** + * Checks if the array contains some of the elements of the specified array. + */ + hasSome?: MapScalarType[]; + + /** + * Checks if the array is empty. + */ + isEmpty?: boolean; + } + : {}); // map a scalar type (primitive and enum) to TS type type MapScalarType = Type extends GetEnums ? keyof GetEnum : MapBaseType; -type PrimitiveFilter = T extends 'String' - ? StringFilter +type PrimitiveFilter< + T extends string, + Nullable extends boolean, + WithAggregations extends boolean, + AllowedKinds extends FilterKind, +> = T extends 'String' + ? StringFilter : T extends 'Int' | 'Float' | 'Decimal' | 'BigInt' - ? NumberFilter + ? NumberFilter : T extends 'Boolean' - ? BooleanFilter + ? BooleanFilter : T extends 'DateTime' - ? DateTimeFilter + ? DateTimeFilter : T extends 'Bytes' - ? BytesFilter + ? BytesFilter : T extends 'Json' - ? JsonFilter + ? JsonFilter : never; type CommonPrimitiveFilter< @@ -415,91 +449,105 @@ type CommonPrimitiveFilter< T extends BuiltinType, Nullable extends boolean, WithAggregations extends boolean, -> = { - /** - * Checks for equality with the specified value. - */ - equals?: NullableIf; - - /** - * Checks if the value is in the specified list of values. - */ - in?: DataType[]; - - /** - * Checks if the value is not in the specified list of values. - */ - notIn?: DataType[]; + AllowedKinds extends FilterKind, +> = ('Equality' extends AllowedKinds + ? { + /** + * Checks for equality with the specified value. + */ + equals?: NullableIf; - /** - * Checks if the value is less than the specified value. - */ - lt?: DataType; + /** + * Checks if the value is in the specified list of values. + */ + in?: DataType[]; - /** - * Checks if the value is less than or equal to the specified value. - */ - lte?: DataType; + /** + * Checks if the value is not in the specified list of values. + */ + notIn?: DataType[]; + } + : {}) & + ('Range' extends AllowedKinds + ? { + /** + * Checks if the value is less than the specified value. + */ + lt?: DataType; - /** - * Checks if the value is greater than the specified value. - */ - gt?: DataType; + /** + * Checks if the value is less than or equal to the specified value. + */ + lte?: DataType; - /** - * Checks if the value is greater than or equal to the specified value. - */ - gte?: DataType; + /** + * Checks if the value is greater than the specified value. + */ + gt?: DataType; - /** - * Checks if the value is between the specified values (inclusive). - */ - between?: [start: DataType, end: DataType]; + /** + * Checks if the value is greater than or equal to the specified value. + */ + gte?: DataType; - /** - * Builds a negated filter. - */ - not?: PrimitiveFilter; -}; + /** + * Checks if the value is between the specified values (inclusive). + */ + between?: [start: DataType, end: DataType]; + } + : {}) & { + /** + * Builds a negated filter. + */ + not?: PrimitiveFilter; + }; -export type StringFilter = - | NullableIf - | (CommonPrimitiveFilter & { - /** - * Checks if the string contains the specified substring. - */ - contains?: string; +export type StringFilter< + Nullable extends boolean, + WithAggregations extends boolean, + AllowedKinds extends FilterKind = FilterKind, +> = + | ('Equality' extends AllowedKinds ? NullableIf : never) + | (CommonPrimitiveFilter & + ('Like' extends AllowedKinds + ? { + /** + * Checks if the string contains the specified substring. + */ + contains?: string; - /** - * Checks if the string starts with the specified substring. - */ - startsWith?: string; + /** + * Checks if the string starts with the specified substring. + */ + startsWith?: string; - /** - * Checks if the string ends with the specified substring. - */ - endsWith?: string; + /** + * Checks if the string ends with the specified substring. + */ + endsWith?: string; - /** - * Specifies the string comparison mode. Not effective for "sqlite" provider - */ - mode?: 'default' | 'insensitive'; - } & (WithAggregations extends true + /** + * Specifies the string comparison mode. Not effective for "sqlite" provider + */ + mode?: 'default' | 'insensitive'; + } + : {}) & + (WithAggregations extends true ? { /** * Filters against the count of records. */ - _count?: NumberFilter<'Int', false, false>; + _count?: NumberFilter<'Int', false, false, AllowedKinds>; /** * Filters against the minimum value. */ - _min?: StringFilter; + _min?: StringFilter; /** * Filters against the maximum value. */ - _max?: StringFilter; + _max?: StringFilter; } : {})); @@ -507,214 +555,254 @@ export type NumberFilter< T extends 'Int' | 'Float' | 'Decimal' | 'BigInt', Nullable extends boolean, WithAggregations extends boolean, + AllowedKinds extends FilterKind = FilterKind, > = - | NullableIf - | (CommonPrimitiveFilter & + | ('Equality' extends AllowedKinds ? NullableIf : never) + | (CommonPrimitiveFilter & (WithAggregations extends true ? { /** * Filters against the count of records. */ - _count?: NumberFilter<'Int', false, false>; + _count?: NumberFilter<'Int', false, false, AllowedKinds>; /** * Filters against the average value. */ - _avg?: NumberFilter; + _avg?: NumberFilter; /** * Filters against the sum value. */ - _sum?: NumberFilter; + _sum?: NumberFilter; /** * Filters against the minimum value. */ - _min?: NumberFilter; + _min?: NumberFilter; /** * Filters against the maximum value. */ - _max?: NumberFilter; + _max?: NumberFilter; } : {})); -export type DateTimeFilter = - | NullableIf - | (CommonPrimitiveFilter & +export type DateTimeFilter< + Nullable extends boolean, + WithAggregations extends boolean, + AllowedKinds extends FilterKind = FilterKind, +> = + | ('Equality' extends AllowedKinds ? NullableIf : never) + | (CommonPrimitiveFilter & (WithAggregations extends true ? { /** * Filters against the count of records. */ - _count?: NumberFilter<'Int', false, false>; + _count?: NumberFilter<'Int', false, false, AllowedKinds>; /** * Filters against the minimum value. */ - _min?: DateTimeFilter; + _min?: DateTimeFilter; /** * Filters against the maximum value. */ - _max?: DateTimeFilter; + _max?: DateTimeFilter; } : {})); -export type BytesFilter = - | NullableIf - | ({ - /** - * Checks for equality with the specified value. - */ - equals?: NullableIf; - - /** - * Checks if the value is in the specified list of values. - */ - in?: Uint8Array[]; - - /** - * Checks if the value is not in the specified list of values. - */ - notIn?: Uint8Array[]; - - /** - * Builds a negated filter. - */ - not?: BytesFilter; - } & (WithAggregations extends true +export type BytesFilter< + Nullable extends boolean, + WithAggregations extends boolean, + AllowedKinds extends FilterKind = FilterKind, +> = + | ('Equality' extends AllowedKinds ? NullableIf : never) + | (('Equality' extends AllowedKinds ? { /** - * Filters against the count of records. + * Checks for equality with the specified value. */ - _count?: NumberFilter<'Int', false, false>; + equals?: NullableIf; /** - * Filters against the minimum value. + * Checks if the value is in the specified list of values. */ - _min?: BytesFilter; + in?: Uint8Array[]; /** - * Filters against the maximum value. + * Checks if the value is not in the specified list of values. */ - _max?: BytesFilter; + notIn?: Uint8Array[]; } - : {})); - -export type BooleanFilter = - | NullableIf - | ({ - /** - * Checks for equality with the specified value. - */ - equals?: NullableIf; - + : {}) & { /** * Builds a negated filter. */ - not?: BooleanFilter; + not?: BytesFilter; } & (WithAggregations extends true - ? { - /** - * Filters against the count of records. - */ - _count?: NumberFilter<'Int', false, false>; + ? { + /** + * Filters against the count of records. + */ + _count?: NumberFilter<'Int', false, false, AllowedKinds>; - /** - * Filters against the minimum value. - */ - _min?: BooleanFilter; + /** + * Filters against the minimum value. + */ + _min?: BytesFilter; + /** + * Filters against the maximum value. + */ + _max?: BytesFilter; + } + : {})); + +export type BooleanFilter< + Nullable extends boolean, + WithAggregations extends boolean, + AllowedKinds extends FilterKind = FilterKind, +> = + | ('Equality' extends AllowedKinds ? NullableIf : never) + | (('Equality' extends AllowedKinds + ? { /** - * Filters against the maximum value. + * Checks for equality with the specified value. */ - _max?: BooleanFilter; + equals?: NullableIf; } - : {})); + : {}) & { + /** + * Builds a negated filter. + */ + not?: BooleanFilter; + } & (WithAggregations extends true + ? { + /** + * Filters against the count of records. + */ + _count?: NumberFilter<'Int', false, false, AllowedKinds>; -export type JsonFilter = { - /** - * JSON path to select the value to filter on. If omitted, the whole JSON value is used. - */ - path?: string; + /** + * Filters against the minimum value. + */ + _min?: BooleanFilter; - /** - * Checks for equality with the specified value. - */ - equals?: JsonValue | JsonNullValues; + /** + * Filters against the maximum value. + */ + _max?: BooleanFilter; + } + : {})); - /** - * Builds a negated filter. - */ - not?: JsonValue | JsonNullValues; +export type JsonFilter = ('Equality' extends AllowedKinds + ? { + /** + * Checks for equality with the specified value. + */ + equals?: JsonValue | JsonNullValues; - /** - * Checks if the value is a string and contains the specified substring. - */ - string_contains?: string; + /** + * Builds a negated filter. + */ + not?: JsonValue | JsonNullValues; + } + : {}) & + ('Json' extends AllowedKinds + ? { + /** + * JSON path to select the value to filter on. If omitted, the whole JSON value is used. + */ + path?: string; - /** - * Checks if the value is a string and starts with the specified substring. - */ - string_starts_with?: string; + /** + * Checks if the value is a string and contains the specified substring. + */ + string_contains?: string; - /** - * Checks if the value is a string and ends with the specified substring. - */ - string_ends_with?: string; + /** + * Checks if the value is a string and starts with the specified substring. + */ + string_starts_with?: string; - /** - * String comparison mode. Not effective for "sqlite" provider - */ - mode?: 'default' | 'insensitive'; + /** + * Checks if the value is a string and ends with the specified substring. + */ + string_ends_with?: string; - /** - * Checks if the value is an array and contains the specified value. - */ - array_contains?: JsonValue; + /** + * String comparison mode. Not effective for "sqlite" provider + */ + mode?: 'default' | 'insensitive'; - /** - * Checks if the value is an array and starts with the specified value. - */ - array_starts_with?: JsonValue; + /** + * Checks if the value is an array and contains the specified value. + */ + array_contains?: JsonValue; - /** - * Checks if the value is an array and ends with the specified value. - */ - array_ends_with?: JsonValue; -}; + /** + * Checks if the value is an array and starts with the specified value. + */ + array_starts_with?: JsonValue; + + /** + * Checks if the value is an array and ends with the specified value. + */ + array_ends_with?: JsonValue; + } + : {}); export type TypedJsonFilter< Schema extends SchemaDef, TypeDefName extends GetTypeDefs, Array extends boolean, Optional extends boolean, -> = XOR>; + AllowedKinds extends FilterKind, +> = XOR, TypedJsonTypedFilter>; type TypedJsonTypedFilter< Schema extends SchemaDef, TypeDefName extends GetTypeDefs, Array extends boolean, Optional extends boolean, -> = - | (Array extends true ? ArrayTypedJsonFilter : NonArrayTypedJsonFilter) - | (Optional extends true ? null : never); - -type ArrayTypedJsonFilter> = { - some?: TypedJsonFieldsFilter; - every?: TypedJsonFieldsFilter; - none?: TypedJsonFieldsFilter; + AllowedKinds extends FilterKind, +> = 'Json' extends AllowedKinds + ? + | (Array extends true + ? ArrayTypedJsonFilter + : NonArrayTypedJsonFilter) + | (Optional extends true ? null : never) + : {}; + +type ArrayTypedJsonFilter< + Schema extends SchemaDef, + TypeDefName extends GetTypeDefs, + AllowedKinds extends FilterKind, +> = { + some?: TypedJsonFieldsFilter; + every?: TypedJsonFieldsFilter; + none?: TypedJsonFieldsFilter; }; -type NonArrayTypedJsonFilter> = +type NonArrayTypedJsonFilter< + Schema extends SchemaDef, + TypeDefName extends GetTypeDefs, + AllowedKinds extends FilterKind, +> = | { - is?: TypedJsonFieldsFilter; - isNot?: TypedJsonFieldsFilter; + is?: TypedJsonFieldsFilter; + isNot?: TypedJsonFieldsFilter; } - | TypedJsonFieldsFilter; + | TypedJsonFieldsFilter; -type TypedJsonFieldsFilter> = { +type TypedJsonFieldsFilter< + Schema extends SchemaDef, + TypeDefName extends GetTypeDefs, + AllowedKinds extends FilterKind, +> = { [Key in GetTypeDefFields]?: GetTypeDefFieldType< Schema, TypeDefName, @@ -725,24 +813,27 @@ type TypedJsonFieldsFilter, TypeDefFieldIsArray, - TypeDefFieldIsOptional + TypeDefFieldIsOptional, + AllowedKinds > : // array TypeDefFieldIsArray extends true - ? ArrayFilter> + ? ArrayFilter, AllowedKinds> : // enum GetTypeDefFieldType extends GetEnums ? EnumFilter< Schema, GetTypeDefFieldType, TypeDefFieldIsOptional, - false + false, + AllowedKinds > : // primitive PrimitiveFilter< GetTypeDefFieldType, TypeDefFieldIsOptional, - false + false, + AllowedKinds >; }; @@ -813,7 +904,11 @@ export type OrderBy< }) : {}); -export type WhereUniqueInput> = AtLeast< +export type WhereUniqueInput< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = AtLeast< { [Key in keyof GetModel['uniqueFields']]?: GetModel< Schema, @@ -829,7 +924,7 @@ export type WhereUniqueInput['uniqueFields'][Key][Key1]> : never; }; - } & WhereInput, + } & WhereInput, Extract['uniqueFields'], string> >; @@ -841,34 +936,38 @@ export type SelectIncludeOmit< Schema extends SchemaDef, Model extends GetModels, AllowCount extends boolean, + Options extends QueryOptions = QueryOptions, AllowRelation extends boolean = true, > = { /** * Explicitly select fields and relations to be returned by the query. */ - select?: SelectInput | null; - - /** - * Specifies relations to be included in the query result. All scalar fields are included. - */ - include?: IncludeInput | null; + select?: SelectInput | null; /** * Explicitly omit fields from the query result. */ omit?: OmitInput | null; -}; +} & (AllowRelation extends true + ? { + /** + * Specifies relations to be included in the query result. All scalar fields are included. + */ + include?: IncludeInput | null; + } + : {}); export type SelectInput< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions = QueryOptions, AllowCount extends boolean = true, AllowRelation extends boolean = true, > = { [Key in NonRelationFields]?: boolean; -} & (AllowRelation extends true ? IncludeInput : {}); +} & (AllowRelation extends true ? IncludeInput : {}); -type SelectCount> = +type SelectCount, Options extends QueryOptions> = | boolean | { /** @@ -878,7 +977,7 @@ type SelectCount> = [Key in RelationFields as FieldIsArray extends true ? Key : never]?: | boolean | { - where: WhereInput, false>; + where: WhereInput, Options, false>; }; }; }; @@ -886,13 +985,20 @@ type SelectCount> = export type IncludeInput< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions = QueryOptions, AllowCount extends boolean = true, > = { - [Key in RelationFields]?: + [Key in RelationFields as RelationFieldType extends GetSlicedModels< + Schema, + Options + > + ? Key + : never]?: | boolean | FindArgs< Schema, RelationFieldType, + Options, FieldIsArray, // where clause is allowed only if the relation is array or optional FieldIsArray extends true @@ -904,7 +1010,7 @@ export type IncludeInput< } & (AllowCount extends true ? // _count is only allowed if the model has to-many relations HasToManyRelations extends true - ? { _count?: SelectCount } + ? { _count?: SelectCount } : {} : {}); @@ -924,23 +1030,25 @@ type ToManyRelationFilter< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = { - every?: WhereInput>; - some?: WhereInput>; - none?: WhereInput>; + every?: WhereInput, Options>; + some?: WhereInput, Options>; + none?: WhereInput, Options>; }; type ToOneRelationFilter< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = NullableIf< - WhereInput> & { + WhereInput, Options> & { /** * Checks if the related record matches the specified filter. */ is?: NullableIf< - WhereInput>, + WhereInput, Options>, ModelFieldIsOptional >; @@ -948,7 +1056,7 @@ type ToOneRelationFilter< * Checks if the related record does not match the specified filter. */ isNot?: NullableIf< - WhereInput>, + WhereInput, Options>, ModelFieldIsOptional >; }, @@ -959,10 +1067,13 @@ type RelationFilter< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, -> = - FieldIsArray extends true - ? ToManyRelationFilter - : ToOneRelationFilter; + Options extends QueryOptions, + AllowedKinds extends FilterKind, +> = 'Relation' extends AllowedKinds + ? FieldIsArray extends true + ? ToManyRelationFilter + : ToOneRelationFilter + : never; //#endregion @@ -995,7 +1106,7 @@ type OptionalFieldsForCreate extends true ? Key - : GetModelField['updatedAt'] extends (true | UpdatedAtInfo) + : GetModelField['updatedAt'] extends true | UpdatedAtInfo ? Key : never]: GetModelField; }; @@ -1045,14 +1156,18 @@ type OppositeRelationAndFK< //#region Find args -type FilterArgs> = { +type FilterArgs, Options extends QueryOptions> = { /** * Filter conditions */ - where?: WhereInput; + where?: WhereInput; }; -type SortAndTakeArgs> = { +type SortAndTakeArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = { /** * Number of records to skip */ @@ -1071,16 +1186,17 @@ type SortAndTakeArgs> /** * Cursor for pagination */ - cursor?: WhereUniqueInput; + cursor?: WhereUniqueInput; }; export type FindArgs< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions, Collection extends boolean, AllowFilter extends boolean = true, > = (Collection extends true - ? SortAndTakeArgs & + ? SortAndTakeArgs & (ProviderSupportsDistinct extends true ? { /** @@ -1090,34 +1206,54 @@ export type FindArgs< } : {}) : {}) & - (AllowFilter extends true ? FilterArgs : {}) & - SelectIncludeOmit; + (AllowFilter extends true ? FilterArgs : {}) & + SelectIncludeOmit; -export type FindManyArgs> = FindArgs; +export type FindManyArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = FindArgs; -export type FindFirstArgs> = FindArgs; +export type FindFirstArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = FindArgs; -export type ExistsArgs> = FilterArgs; +export type ExistsArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = FilterArgs; -export type FindUniqueArgs> = { - where: WhereUniqueInput; -} & SelectIncludeOmit; +export type FindUniqueArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { + where: WhereUniqueInput; +} & SelectIncludeOmit; //#endregion //#region Create args -export type CreateArgs> = { - data: CreateInput; -} & SelectIncludeOmit; +export type CreateArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { + data: CreateInput; +} & SelectIncludeOmit; export type CreateManyArgs> = CreateManyInput; -export type CreateManyAndReturnArgs> = CreateManyInput< - Schema, - Model -> & - Omit, 'include'>; +export type CreateManyAndReturnArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = CreateManyInput & SelectIncludeOmit; type OptionalWrap, T extends object> = Optional< T, @@ -1176,17 +1312,18 @@ type CreateRelationFieldPayload< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = Omit< { /** * Connects or create a related record. */ - connectOrCreate?: ConnectOrCreateInput; + connectOrCreate?: ConnectOrCreateInput; /** * Creates a related record. */ - create?: NestedCreateInput; + create?: NestedCreateInput; /** * Creates a batch of related records. @@ -1196,7 +1333,7 @@ type CreateRelationFieldPayload< /** * Connects an existing record. */ - connect?: ConnectInput; + connect?: ConnectInput; }, // no "createMany" for non-array fields | (FieldIsArray extends true ? never : 'createMany') @@ -1204,50 +1341,73 @@ type CreateRelationFieldPayload< | (FieldIsDelegateRelation extends true ? 'create' | 'createMany' | 'connectOrCreate' : never) >; -type CreateRelationPayload> = OptionalWrap< +type CreateRelationPayload< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = OptionalWrap< Schema, Model, { - [Key in RelationFields]: CreateRelationFieldPayload; + [Key in RelationFields as RelationFieldType extends GetSlicedModels< + Schema, + Options + > + ? Key + : never]: CreateRelationFieldPayload; } >; -type CreateWithFKInput> = +type CreateWithFKInput< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = // scalar fields CreateScalarPayload & // fk fields CreateFKPayload & // non-owned relations - CreateWithNonOwnedRelationPayload; + CreateWithNonOwnedRelationPayload; -type CreateWithRelationInput> = CreateScalarPayload< - Schema, - Model -> & - CreateRelationPayload; +type CreateWithRelationInput< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = CreateScalarPayload & CreateRelationPayload; -type CreateWithNonOwnedRelationPayload> = OptionalWrap< +type CreateWithNonOwnedRelationPayload< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = OptionalWrap< Schema, Model, { - [Key in NonOwnedRelationFields]: CreateRelationFieldPayload; + [Key in NonOwnedRelationFields as RelationFieldType extends GetSlicedModels< + Schema, + Options + > + ? Key + : never]: CreateRelationFieldPayload; } >; type ConnectOrCreatePayload< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions, Without extends string = never, > = { /** * The unique filter to find an existing record to connect. */ - where: WhereUniqueInput; + where: WhereUniqueInput; /** * The data to create a new record if no existing record is found. */ - create: CreateInput; + create: CreateInput; }; export type CreateManyInput< @@ -1269,15 +1429,20 @@ export type CreateManyInput< export type CreateInput< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions, Without extends string = never, -> = XOR, Without>, Omit, Without>>; +> = XOR< + Omit, Without>, + Omit, Without> +>; type NestedCreateInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = OrArray< - CreateInput, OppositeRelationAndFK>, + CreateInput, Options, OppositeRelationAndFK>, FieldIsArray >; @@ -1291,30 +1456,40 @@ type NestedCreateManyInput< // #region Update args -export type UpdateArgs> = { +export type UpdateArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { /** * The data to update the record with. */ - data: UpdateInput; + data: UpdateInput; /** * The unique filter to find the record to update. */ - where: WhereUniqueInput; -} & SelectIncludeOmit; + where: WhereUniqueInput; +} & SelectIncludeOmit; -export type UpdateManyArgs> = UpdateManyPayload< - Schema, - Model ->; +export type UpdateManyArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = UpdateManyPayload; -export type UpdateManyAndReturnArgs> = UpdateManyPayload< - Schema, - Model -> & - Omit, 'include'>; +export type UpdateManyAndReturnArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = UpdateManyPayload & SelectIncludeOmit; -type UpdateManyPayload, Without extends string = never> = { +type UpdateManyPayload< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, + Without extends string = never, +> = { /** * The data to update the records with. */ @@ -1323,7 +1498,7 @@ type UpdateManyPayload /** * The filter to select records to update. */ - where?: WhereInput; + where?: WhereInput; /** * Limit the number of records to update. @@ -1331,22 +1506,26 @@ type UpdateManyPayload limit?: number; }; -export type UpsertArgs> = { +export type UpsertArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { /** * The data to create the record if it doesn't exist. */ - create: CreateInput; + create: CreateInput; /** * The data to update the record with if it exists. */ - update: UpdateInput; + update: UpdateInput; /** * The unique filter to find the record to update. */ - where: WhereUniqueInput; -} & SelectIncludeOmit; + where: WhereUniqueInput; +} & SelectIncludeOmit; type UpdateScalarInput< Schema extends SchemaDef, @@ -1413,10 +1592,16 @@ type ScalarUpdatePayload< type UpdateRelationInput< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions, Without extends string = never, > = Omit< { - [Key in RelationFields]?: UpdateRelationFieldPayload; + [Key in RelationFields as RelationFieldType extends GetSlicedModels< + Schema, + Options + > + ? Key + : never]?: UpdateRelationFieldPayload; }, Without >; @@ -1424,28 +1609,30 @@ type UpdateRelationInput< export type UpdateInput< Schema extends SchemaDef, Model extends GetModels, + Options extends QueryOptions, Without extends string = never, -> = UpdateScalarInput & UpdateRelationInput; - +> = UpdateScalarInput & UpdateRelationInput; type UpdateRelationFieldPayload< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = FieldIsArray extends true - ? ToManyRelationUpdateInput - : ToOneRelationUpdateInput; + ? ToManyRelationUpdateInput + : ToOneRelationUpdateInput; type ToManyRelationUpdateInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = Omit< { /** * Creates related records. */ - create?: NestedCreateInput; + create?: NestedCreateInput; /** * Creates a batch of related records. @@ -1455,47 +1642,47 @@ type ToManyRelationUpdateInput< /** * Connects existing records. */ - connect?: ConnectInput; + connect?: ConnectInput; /** * Connects or create related records. */ - connectOrCreate?: ConnectOrCreateInput; + connectOrCreate?: ConnectOrCreateInput; /** * Disconnects related records. */ - disconnect?: DisconnectInput; + disconnect?: DisconnectInput; /** * Updates related records. */ - update?: NestedUpdateInput; + update?: NestedUpdateInput; /** * Upserts related records. */ - upsert?: NestedUpsertInput; + upsert?: NestedUpsertInput; /** * Updates a batch of related records. */ - updateMany?: NestedUpdateManyInput; + updateMany?: NestedUpdateManyInput; /** * Deletes related records. */ - delete?: NestedDeleteInput; + delete?: NestedDeleteInput; /** * Deletes a batch of related records. */ - deleteMany?: NestedDeleteManyInput; + deleteMany?: NestedDeleteManyInput; /** * Sets the related records to the specified ones. */ - set?: SetRelationInput; + set?: SetRelationInput; }, // exclude FieldIsDelegateRelation extends true @@ -1507,43 +1694,44 @@ type ToOneRelationUpdateInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = Omit< { /** * Creates a related record. */ - create?: NestedCreateInput; + create?: NestedCreateInput; /** * Connects an existing record. */ - connect?: ConnectInput; + connect?: ConnectInput; /** * Connects or create a related record. */ - connectOrCreate?: ConnectOrCreateInput; + connectOrCreate?: ConnectOrCreateInput; /** * Updates the related record. */ - update?: NestedUpdateInput; + update?: NestedUpdateInput; /** * Upserts the related record. */ - upsert?: NestedUpsertInput; + upsert?: NestedUpsertInput; } & (ModelFieldIsOptional extends true ? { /** * Disconnects the related record. */ - disconnect?: DisconnectInput; + disconnect?: DisconnectInput; /** * Deletes the related record. */ - delete?: NestedDeleteInput; + delete?: NestedDeleteInput; } : {}), FieldIsDelegateRelation extends true ? 'create' | 'connectOrCreate' | 'upsert' : never @@ -1553,18 +1741,26 @@ type ToOneRelationUpdateInput< // #region Delete args -export type DeleteArgs> = { +export type DeleteArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { /** * The unique filter to find the record to delete. */ - where: WhereUniqueInput; -} & SelectIncludeOmit; + where: WhereUniqueInput; +} & SelectIncludeOmit; -export type DeleteManyArgs> = { +export type DeleteManyArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { /** * Filter to select records to delete. */ - where?: WhereInput; + where?: WhereInput; /** * Limits the number of records to delete. @@ -1576,10 +1772,11 @@ export type DeleteManyArgs> = Omit< - FindArgs, - 'select' | 'include' | 'distinct' | 'omit' -> & { +export type CountArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = Omit, 'select' | 'include' | 'distinct' | 'omit'> & { /** * Selects fields to count */ @@ -1604,12 +1801,15 @@ export type CountResult> = { +export type AggregateArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { /** * Filter conditions */ - where?: WhereInput; - + where?: WhereInput; /** * Number of records to skip for the aggregation */ @@ -1732,16 +1932,21 @@ type AggCommonOutput = Input extends true // #region GroupBy -type GroupByHaving> = Omit< - WhereInput, - '$expr' ->; +type GroupByHaving< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = Omit, '$expr'>; -export type GroupByArgs> = { +export type GroupByArgs< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions = QueryOptions, +> = { /** * Filter conditions */ - where?: WhereInput; + where?: WhereInput; /** * Order by clauses @@ -1756,7 +1961,7 @@ export type GroupByArgs; + having?: GroupByHaving; /** * Number of records to take for grouping @@ -1855,27 +2060,31 @@ type ConnectInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = FieldIsArray extends true - ? OrArray>> - : WhereUniqueInput>; + ? OrArray, Options>> + : WhereUniqueInput, Options>; type ConnectOrCreateInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = FieldIsArray extends true ? OrArray< ConnectOrCreatePayload< Schema, RelationFieldType, + Options, OppositeRelationAndFK > > : ConnectOrCreatePayload< Schema, RelationFieldType, + Options, OppositeRelationAndFK >; @@ -1883,21 +2092,24 @@ type DisconnectInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = FieldIsArray extends true - ? OrArray>, true> - : boolean | WhereInput>; + ? OrArray, Options>, true> + : boolean | WhereInput, Options>; type SetRelationInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, -> = OrArray>>; + Options extends QueryOptions, +> = OrArray, Options>>; type NestedUpdateInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = FieldIsArray extends true ? // to-many @@ -1906,7 +2118,7 @@ type NestedUpdateInput< /** * Unique filter to select the record to update. */ - where: WhereUniqueInput>; + where: WhereUniqueInput, Options>; /** * The data to update the record with. @@ -1914,6 +2126,7 @@ type NestedUpdateInput< data: UpdateInput< Schema, RelationFieldType, + Options, OppositeRelationAndFK >; }, @@ -1925,7 +2138,7 @@ type NestedUpdateInput< /** * Filter to select the record to update. */ - where?: WhereInput>; + where?: WhereInput, Options>; /** * The data to update the record with. @@ -1933,22 +2146,29 @@ type NestedUpdateInput< data: UpdateInput< Schema, RelationFieldType, + Options, OppositeRelationAndFK >; }, - UpdateInput, OppositeRelationAndFK> + UpdateInput< + Schema, + RelationFieldType, + Options, + OppositeRelationAndFK + > >; type NestedUpsertInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = OrArray< { /** * Unique filter to select the record to update. */ - where: WhereUniqueInput>; + where: WhereUniqueInput, Options>; /** * The data to create the record if it doesn't exist. @@ -1956,6 +2176,7 @@ type NestedUpsertInput< create: CreateInput< Schema, RelationFieldType, + Options, OppositeRelationAndFK >; @@ -1965,6 +2186,7 @@ type NestedUpsertInput< update: UpdateInput< Schema, RelationFieldType, + Options, OppositeRelationAndFK >; }, @@ -1975,24 +2197,32 @@ type NestedUpdateManyInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = OrArray< - UpdateManyPayload, OppositeRelationAndFK> + UpdateManyPayload< + Schema, + RelationFieldType, + Options, + OppositeRelationAndFK + > >; type NestedDeleteInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, + Options extends QueryOptions, > = FieldIsArray extends true - ? OrArray>, true> - : boolean | WhereInput>; + ? OrArray, Options>, true> + : boolean | WhereInput, Options>; type NestedDeleteManyInput< Schema extends SchemaDef, Model extends GetModels, Field extends RelationFields, -> = OrArray, true>>; + Options extends QueryOptions, +> = OrArray, Options, true>>; // #endregion diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index 9b273e2b6..0f204817f 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -5,7 +5,7 @@ import { match, P } from 'ts-pattern'; import { AnyNullClass, DbNullClass, JsonNullClass } from '../../../common-types'; import type { BuiltinType, DataSourceProviderType, FieldDef, GetModels, ModelDef, SchemaDef } from '../../../schema'; import type { OrArray } from '../../../utils/type-utils'; -import { AGGREGATE_OPERATORS, DELEGATE_JOINED_FIELD_PREFIX, LOGICAL_COMBINATORS } from '../../constants'; +import { AggregateOperators, DELEGATE_JOINED_FIELD_PREFIX, LOGICAL_COMBINATORS } from '../../constants'; import type { BooleanFilter, BytesFilter, @@ -117,7 +117,7 @@ export abstract class BaseCrudDialect { buildFilterSortTake( model: string, - args: FindArgs, true>, + args: FindArgs, any, true>, query: SelectQueryBuilder, modelAlias: string, ) { @@ -828,7 +828,7 @@ export abstract class BaseCrudDialect { }) .with('not', () => this.eb.not(recurse(value))) // aggregations - .with(P.union(...AGGREGATE_OPERATORS), (op) => { + .with(P.union(...AggregateOperators), (op) => { const innerResult = this.buildStandardFilter( type, value, @@ -1040,7 +1040,7 @@ export abstract class BaseCrudDialect { for (const [k, v] of Object.entries(value)) { invariant(v === 'asc' || v === 'desc', `invalid orderBy value for field "${field}"`); result = result.orderBy( - (eb) => aggregate(eb, buildFieldRef(model, k, modelAlias), field as AGGREGATE_OPERATORS), + (eb) => aggregate(eb, buildFieldRef(model, k, modelAlias), field as AggregateOperators), this.negateSort(v, negated), ); } @@ -1164,7 +1164,6 @@ export abstract class BaseCrudDialect { return (omit as any)[field]; } - // options-level if ( this.options.omit?.[model] && typeof this.options.omit[model] === 'object' && @@ -1181,7 +1180,7 @@ export abstract class BaseCrudDialect { protected buildModelSelect( model: GetModels, subQueryAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, selectAllFields: boolean, ) { let subQuery = this.buildSelectModel(model, subQueryAlias); @@ -1371,7 +1370,7 @@ export abstract class BaseCrudDialect { protected canJoinWithoutNestedSelect( modelDef: ModelDef, - payload: boolean | FindArgs, true>, + payload: boolean | FindArgs, any, true>, ) { if (modelDef.computedFields) { // computed fields requires explicit select @@ -1412,7 +1411,7 @@ export abstract class BaseCrudDialect { model: string, relationField: string, parentAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, ): SelectQueryBuilder; /** diff --git a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts index 6bc1f887c..65f2d9cec 100644 --- a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts +++ b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts @@ -29,7 +29,7 @@ export abstract class LateralJoinDialectBase extends B model: string, relationField: string, parentAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, ): SelectQueryBuilder { const relationResultName = `${parentAlias}$${relationField}`; const joinedQuery = this.buildRelationJSON( @@ -48,7 +48,7 @@ export abstract class LateralJoinDialectBase extends B qb: SelectQueryBuilder, relationField: string, parentAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, resultName: string, ) { const relationFieldDef = requireField(this.schema, model, relationField); @@ -158,7 +158,7 @@ export abstract class LateralJoinDialectBase extends B relationModelAlias: string, relationFieldDef: FieldDef, qb: SelectQueryBuilder, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, parentResultName: string, ) { qb = qb.select((eb) => { @@ -184,7 +184,7 @@ export abstract class LateralJoinDialectBase extends B relationModel: string, relationModelAlias: string, eb: ExpressionBuilder, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, parentResultName: string, ) { const relationModelDef = requireModel(this.schema, relationModel); @@ -264,7 +264,7 @@ export abstract class LateralJoinDialectBase extends B query: SelectQueryBuilder, relationModel: string, relationModelAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, parentResultName: string, ) { let result = query; diff --git a/packages/orm/src/client/crud/dialects/sqlite.ts b/packages/orm/src/client/crud/dialects/sqlite.ts index 75dd25dcc..93d4f547d 100644 --- a/packages/orm/src/client/crud/dialects/sqlite.ts +++ b/packages/orm/src/client/crud/dialects/sqlite.ts @@ -183,7 +183,7 @@ export class SqliteCrudDialect extends BaseCrudDialect model: string, relationField: string, parentAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, ): SelectQueryBuilder { return query.select((eb) => this.buildRelationJSON(model, eb, relationField, parentAlias, payload).as(relationField), @@ -195,7 +195,7 @@ export class SqliteCrudDialect extends BaseCrudDialect eb: ExpressionBuilder, relationField: string, parentAlias: string, - payload: true | FindArgs, true>, + payload: true | FindArgs, any, true>, ) { const relationFieldDef = requireField(this.schema, model, relationField); const relationModel = relationFieldDef.type as GetModels; diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index fc75cac9d..78dffd843 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -272,7 +272,7 @@ export abstract class BaseOperationHandler { protected async read( kysely: AnyKysely, model: string, - args: FindArgs, true> | undefined, + args: FindArgs, any, true> | undefined, ): Promise { // table let query = this.dialect.buildSelectModel(model, model); @@ -310,7 +310,7 @@ export abstract class BaseOperationHandler { return result; } - protected async readUnique(kysely: AnyKysely, model: string, args: FindArgs, true>) { + protected async readUnique(kysely: AnyKysely, model: string, args: FindArgs, any, true>) { const result = await this.read(kysely, model, { ...args, take: 1 }); return result[0] ?? null; } @@ -1137,7 +1137,7 @@ export abstract class BaseOperationHandler { const parentWhere = await this.buildUpdateParentRelationFilter(kysely, fromRelation); - let combinedWhere: WhereInput, false> = where ?? {}; + let combinedWhere: WhereInput, any, false> = where ?? {}; if (Object.keys(parentWhere).length > 0) { combinedWhere = Object.keys(combinedWhere).length > 0 ? { AND: [parentWhere, combinedWhere] } : parentWhere; } @@ -1538,7 +1538,7 @@ export abstract class BaseOperationHandler { } const parentWhere = await this.buildUpdateParentRelationFilter(kysely, fromRelation); - let combinedWhere: WhereInput, false> = where ?? {}; + let combinedWhere: WhereInput, any, false> = where ?? {}; if (Object.keys(parentWhere).length > 0) { combinedWhere = Object.keys(combinedWhere).length > 0 ? { AND: [parentWhere, combinedWhere] } : parentWhere; } diff --git a/packages/orm/src/client/crud/operations/create.ts b/packages/orm/src/client/crud/operations/create.ts index eeb0802d5..af3c85994 100644 --- a/packages/orm/src/client/crud/operations/create.ts +++ b/packages/orm/src/client/crud/operations/create.ts @@ -1,6 +1,5 @@ import { match } from 'ts-pattern'; -import type { GetModels, SchemaDef } from '../../../schema'; -import type { CreateArgs, CreateManyAndReturnArgs, CreateManyArgs, WhereInput } from '../../crud-types'; +import type { SchemaDef } from '../../../schema'; import { createRejectedByPolicyError, RejectedByPolicyReason } from '../../errors'; import { getIdValues } from '../../query-utils'; import { BaseOperationHandler } from './base'; @@ -23,7 +22,7 @@ export class CreateOperationHandler extends BaseOperat .exhaustive(); } - private async runCreate(args: CreateArgs>) { + private async runCreate(args: any) { // analyze if we need to read back the created record, or just return the create result const { needReadBack, selectedFields } = this.mutationNeedsReadBack(this.model, args); @@ -36,11 +35,7 @@ export class CreateOperationHandler extends BaseOperat select: args.select, include: args.include, omit: args.omit, - where: getIdValues(this.schema, this.model, createResult) as WhereInput< - Schema, - GetModels, - false - >, + where: getIdValues(this.schema, this.model, createResult) as any, }); } else { return createResult; @@ -58,7 +53,7 @@ export class CreateOperationHandler extends BaseOperat return result; } - private runCreateMany(args?: CreateManyArgs>) { + private runCreateMany(args?: any) { if (args === undefined) { return { count: 0 }; } @@ -66,7 +61,7 @@ export class CreateOperationHandler extends BaseOperat return this.safeTransaction((tx) => this.createMany(tx, this.model, args, false)); } - private async runCreateManyAndReturn(args?: CreateManyAndReturnArgs>) { + private async runCreateManyAndReturn(args?: any) { if (args === undefined) { return []; } diff --git a/packages/orm/src/client/crud/operations/delete.ts b/packages/orm/src/client/crud/operations/delete.ts index e0c3875b5..295eba799 100644 --- a/packages/orm/src/client/crud/operations/delete.ts +++ b/packages/orm/src/client/crud/operations/delete.ts @@ -1,6 +1,5 @@ import { match } from 'ts-pattern'; import type { SchemaDef } from '../../../schema'; -import type { DeleteArgs, DeleteManyArgs } from '../../crud-types'; import { createNotFoundError, createRejectedByPolicyError, RejectedByPolicyReason } from '../../errors'; import { BaseOperationHandler } from './base'; @@ -17,7 +16,7 @@ export class DeleteOperationHandler extends BaseOperat .exhaustive(); } - async runDelete(args: DeleteArgs>) { + async runDelete(args: any) { // analyze if we need to read back the deleted record, or just return delete result const { needReadBack, selectedFields } = this.mutationNeedsReadBack(this.model, args); @@ -51,7 +50,7 @@ export class DeleteOperationHandler extends BaseOperat return result; } - async runDeleteMany(args: DeleteManyArgs> | undefined) { + async runDeleteMany(args?: any) { return await this.safeTransaction(async (tx) => { const result = await this.delete(tx, this.model, args?.where, args?.limit); return { count: Number(result.numAffectedRows ?? 0) }; diff --git a/packages/orm/src/client/crud/operations/find.ts b/packages/orm/src/client/crud/operations/find.ts index db087a3b5..7bf56b8f5 100644 --- a/packages/orm/src/client/crud/operations/find.ts +++ b/packages/orm/src/client/crud/operations/find.ts @@ -1,5 +1,4 @@ -import type { GetModels, SchemaDef } from '../../../schema'; -import type { FindArgs } from '../../crud-types'; +import type { SchemaDef } from '../../../schema'; import { BaseOperationHandler, type CoreCrudOperations } from './base'; export class FindOperationHandler extends BaseOperationHandler { @@ -12,7 +11,7 @@ export class FindOperationHandler extends BaseOperatio // parse args let parsedArgs = validateArgs ? this.inputValidator.validateFindArgs(this.model, normalizedArgs, operation) - : (normalizedArgs as FindArgs, true> | undefined); + : (normalizedArgs as any); if (findOne) { // ensure "limit 1" diff --git a/packages/orm/src/client/crud/operations/update.ts b/packages/orm/src/client/crud/operations/update.ts index d8bd57b53..424945708 100644 --- a/packages/orm/src/client/crud/operations/update.ts +++ b/packages/orm/src/client/crud/operations/update.ts @@ -1,6 +1,6 @@ import { match } from 'ts-pattern'; import type { GetModels, SchemaDef } from '../../../schema'; -import type { UpdateArgs, UpdateManyAndReturnArgs, UpdateManyArgs, UpsertArgs, WhereInput } from '../../crud-types'; +import type { WhereInput } from '../../crud-types'; import { createRejectedByPolicyError, RejectedByPolicyReason } from '../../errors'; import { getIdValues } from '../../query-utils'; import { BaseOperationHandler } from './base'; @@ -24,7 +24,7 @@ export class UpdateOperationHandler extends BaseOperat .exhaustive(); } - private async runUpdate(args: UpdateArgs>) { + private async runUpdate(args: any) { // analyze if we need to read back the update record, or just return the updated result const { needReadBack, selectedFields } = this.needReadBack(args); @@ -50,7 +50,7 @@ export class UpdateOperationHandler extends BaseOperat select: args.select, include: args.include, omit: args.omit, - where: readFilter as WhereInput, false>, + where: readFilter, }); return readBackResult; } else { @@ -77,14 +77,14 @@ export class UpdateOperationHandler extends BaseOperat } } - private async runUpdateMany(args: UpdateManyArgs>) { + private async runUpdateMany(args: any) { // TODO: avoid using transaction for simple update return this.safeTransaction(async (tx) => { return this.updateMany(tx, this.model, args.where, args.data, args.limit, false); }); } - private async runUpdateManyAndReturn(args: UpdateManyAndReturnArgs> | undefined) { + private async runUpdateManyAndReturn(args: any) { if (!args) { return []; } @@ -136,7 +136,7 @@ export class UpdateOperationHandler extends BaseOperat return readBackResult; } - private async runUpsert(args: UpsertArgs>) { + private async runUpsert(args: any) { // analyze if we need to read back the updated record, or just return the update result const { needReadBack, selectedFields } = this.needReadBack(args); @@ -165,6 +165,7 @@ export class UpdateOperationHandler extends BaseOperat where: getIdValues(this.schema, this.model, mutationResult) as WhereInput< Schema, GetModels, + any, false >, }); diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 8cad792e9..88bf0cfd1 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -1,4 +1,4 @@ -import { enumerate, invariant } from '@zenstackhq/common-helpers'; +import { enumerate, invariant, lowerCaseFirst } from '@zenstackhq/common-helpers'; import Decimal from 'decimal.js'; import { match, P } from 'ts-pattern'; import { z, ZodObject, ZodType } from 'zod'; @@ -13,7 +13,7 @@ import { } from '../../../schema'; import { extractFields } from '../../../utils/object-utils'; import { formatError } from '../../../utils/zod-utils'; -import { AGGREGATE_OPERATORS, LOGICAL_COMBINATORS, NUMERIC_FIELD_TYPES } from '../../constants'; +import { AggregateOperators, FILTER_PROPERTY_TO_KIND, LOGICAL_COMBINATORS, NUMERIC_FIELD_TYPES } from '../../constants'; import type { ClientContract } from '../../contract'; import { type AggregateArgs, @@ -63,8 +63,19 @@ import { type GetSchemaFunc = (model: GetModels) => ZodType; +/** + * Minimal field information needed for filter schema generation. + */ +type FieldInfo = { + name: string; + type: string; + optional?: boolean; + array?: boolean; +}; + export class InputValidator { private readonly schemaCache = new Map(); + private readonly allFilterKinds = [...new Set(Object.values(FILTER_PROPERTY_TO_KIND))]; constructor(private readonly client: ClientContract) {} @@ -86,8 +97,8 @@ export class InputValidator { model: GetModels, args: unknown, operation: CoreCrudOperations, - ): FindArgs, true> | undefined { - return this.validate, true> | undefined>( + ): FindArgs, any, true> | undefined { + return this.validate, any, true> | undefined>( model, operation, (model) => this.makeFindSchema(model, operation), @@ -95,8 +106,11 @@ export class InputValidator { ); } - validateExistsArgs(model: GetModels, args: unknown): ExistsArgs> | undefined { - return this.validate>>( + validateExistsArgs( + model: GetModels, + args: unknown, + ): ExistsArgs, any> | undefined { + return this.validate, any> | undefined>( model, 'exists', (model) => this.makeExistsSchema(model), @@ -104,8 +118,8 @@ export class InputValidator { ); } - validateCreateArgs(model: GetModels, args: unknown): CreateArgs> { - return this.validate>>( + validateCreateArgs(model: GetModels, args: unknown): CreateArgs, any> { + return this.validate, any>>( model, 'create', (model) => this.makeCreateSchema(model), @@ -125,8 +139,8 @@ export class InputValidator { validateCreateManyAndReturnArgs( model: GetModels, args: unknown, - ): CreateManyAndReturnArgs> | undefined { - return this.validate> | undefined>( + ): CreateManyAndReturnArgs, any> | undefined { + return this.validate, any> | undefined>( model, 'createManyAndReturn', (model) => this.makeCreateManyAndReturnSchema(model), @@ -134,8 +148,8 @@ export class InputValidator { ); } - validateUpdateArgs(model: GetModels, args: unknown): UpdateArgs> { - return this.validate>>( + validateUpdateArgs(model: GetModels, args: unknown): UpdateArgs, any> { + return this.validate, any>>( model, 'update', (model) => this.makeUpdateSchema(model), @@ -143,8 +157,8 @@ export class InputValidator { ); } - validateUpdateManyArgs(model: GetModels, args: unknown): UpdateManyArgs> { - return this.validate>>( + validateUpdateManyArgs(model: GetModels, args: unknown): UpdateManyArgs, any> { + return this.validate, any>>( model, 'updateMany', (model) => this.makeUpdateManySchema(model), @@ -155,8 +169,8 @@ export class InputValidator { validateUpdateManyAndReturnArgs( model: GetModels, args: unknown, - ): UpdateManyAndReturnArgs> { - return this.validate>>( + ): UpdateManyAndReturnArgs, any> { + return this.validate, any>>( model, 'updateManyAndReturn', (model) => this.makeUpdateManyAndReturnSchema(model), @@ -164,8 +178,8 @@ export class InputValidator { ); } - validateUpsertArgs(model: GetModels, args: unknown): UpsertArgs> { - return this.validate>>( + validateUpsertArgs(model: GetModels, args: unknown): UpsertArgs, any> { + return this.validate, any>>( model, 'upsert', (model) => this.makeUpsertSchema(model), @@ -173,8 +187,8 @@ export class InputValidator { ); } - validateDeleteArgs(model: GetModels, args: unknown): DeleteArgs> { - return this.validate>>( + validateDeleteArgs(model: GetModels, args: unknown): DeleteArgs, any> { + return this.validate, any>>( model, 'delete', (model) => this.makeDeleteSchema(model), @@ -185,8 +199,8 @@ export class InputValidator { validateDeleteManyArgs( model: GetModels, args: unknown, - ): DeleteManyArgs> | undefined { - return this.validate> | undefined>( + ): DeleteManyArgs, any> | undefined { + return this.validate, any> | undefined>( model, 'deleteMany', (model) => this.makeDeleteManySchema(model), @@ -194,8 +208,8 @@ export class InputValidator { ); } - validateCountArgs(model: GetModels, args: unknown): CountArgs> | undefined { - return this.validate> | undefined>( + validateCountArgs(model: GetModels, args: unknown): CountArgs, any> | undefined { + return this.validate, any> | undefined>( model, 'count', (model) => this.makeCountSchema(model), @@ -203,8 +217,8 @@ export class InputValidator { ); } - validateAggregateArgs(model: GetModels, args: unknown): AggregateArgs> { - return this.validate>>( + validateAggregateArgs(model: GetModels, args: unknown): AggregateArgs, any> { + return this.validate, any>>( model, 'aggregate', (model) => this.makeAggregateSchema(model), @@ -212,8 +226,8 @@ export class InputValidator { ); } - validateGroupByArgs(model: GetModels, args: unknown): GroupByArgs> { - return this.validate>>( + validateGroupByArgs(model: GetModels, args: unknown): GroupByArgs, any> { + return this.validate, any>>( model, 'groupBy', (model) => this.makeGroupBySchema(model), @@ -546,6 +560,17 @@ export class InputValidator { ): ZodType { const modelDef = requireModel(this.schema, model); + // unique field used in unique filters bypass filter slicing + const uniqueFieldNames = unique + ? getUniqueFields(this.schema, model) + .filter( + (uf): uf is { name: string; def: FieldDef } => + // single-field unique + 'def' in uf, + ) + .map((uf) => uf.name) + : undefined; + const fields: Record = {}; for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); @@ -555,55 +580,56 @@ export class InputValidator { if (withoutRelationFields) { continue; } - fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional()); - - // optional to-one relation allows null - fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional); - if (fieldDef.array) { - // to-many relation - fieldSchema = z.union([ - fieldSchema, - z.strictObject({ - some: fieldSchema.optional(), - every: fieldSchema.optional(), - none: fieldSchema.optional(), - }), - ]); + // Check if Relation filter kind is allowed + const allowedFilterKinds = this.getEffectiveFilterKinds(model, field); + if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) { + // Relation filters are not allowed for this field - use z.never() + fieldSchema = z.never(); } else { - // to-one relation - fieldSchema = z.union([ - fieldSchema, - z.strictObject({ - is: fieldSchema.optional(), - isNot: fieldSchema.optional(), - }), - ]); + fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional()); + + // optional to-one relation allows null + fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional); + + if (fieldDef.array) { + // to-many relation + fieldSchema = z.union([ + fieldSchema, + z.strictObject({ + some: fieldSchema.optional(), + every: fieldSchema.optional(), + none: fieldSchema.optional(), + }), + ]); + } else { + // to-one relation + fieldSchema = z.union([ + fieldSchema, + z.strictObject({ + is: fieldSchema.optional(), + isNot: fieldSchema.optional(), + }), + ]); + } } } else { + const ignoreSlicing = !!uniqueFieldNames?.includes(field); + const enumDef = getEnum(this.schema, fieldDef.type); if (enumDef) { // enum if (Object.keys(enumDef.values).length > 0) { - fieldSchema = this.makeEnumFilterSchema( - fieldDef.type, - !!fieldDef.optional, - withAggregations, - !!fieldDef.array, - ); + fieldSchema = this.makeEnumFilterSchema(model, fieldDef, withAggregations, ignoreSlicing); } } else if (fieldDef.array) { // array field - fieldSchema = this.makeArrayFilterSchema(fieldDef.type as BuiltinType); + fieldSchema = this.makeArrayFilterSchema(model, fieldDef); } else if (this.isTypeDefType(fieldDef.type)) { - fieldSchema = this.makeTypedJsonFilterSchema(fieldDef.type, !!fieldDef.optional, !!fieldDef.array); + fieldSchema = this.makeTypedJsonFilterSchema(model, fieldDef); } else { // primitive field - fieldSchema = this.makePrimitiveFilterSchema( - fieldDef.type as BuiltinType, - !!fieldDef.optional, - withAggregations, - ); + fieldSchema = this.makePrimitiveFilterSchema(model, fieldDef, withAggregations, ignoreSlicing); } } @@ -614,6 +640,7 @@ export class InputValidator { if (unique) { // add compound unique fields, e.g. `{ id1_id2: { id1: 1, id2: 1 } }` + // compound-field filters are not affected by slicing const uniqueFields = getUniqueFields(this.schema, model); for (const uniqueField of uniqueFields) { if ('defs' in uniqueField) { @@ -627,22 +654,12 @@ export class InputValidator { if (enumDef) { // enum if (Object.keys(enumDef.values).length > 0) { - fieldSchema = this.makeEnumFilterSchema( - def.type, - !!def.optional, - false, - false, - ); + fieldSchema = this.makeEnumFilterSchema(model, def, false, true); } else { fieldSchema = z.never(); } } else { - // regular field - fieldSchema = this.makePrimitiveFilterSchema( - def.type as BuiltinType, - !!def.optional, - false, - ); + fieldSchema = this.makePrimitiveFilterSchema(model, def, false, true); } return [key, fieldSchema]; }), @@ -697,7 +714,12 @@ export class InputValidator { } @cache() - private makeTypedJsonFilterSchema(type: string, optional: boolean, array: boolean) { + private makeTypedJsonFilterSchema(contextModel: string | undefined, fieldInfo: FieldInfo) { + const field = fieldInfo.name; + const type = fieldInfo.type; + const optional = !!fieldInfo.optional; + const array = !!fieldInfo.array; + const typeDef = getTypeDef(this.schema, type); invariant(typeDef, `Type definition "${type}" not found in schema`); @@ -708,28 +730,19 @@ export class InputValidator { const fieldSchemas: Record = {}; for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { if (this.isTypeDefType(fieldDef.type)) { - // recursive typed JSON - fieldSchemas[fieldName] = this.makeTypedJsonFilterSchema( - fieldDef.type, - !!fieldDef.optional, - !!fieldDef.array, - ).optional(); + // recursive typed JSON - use same model/field for nested typed JSON + fieldSchemas[fieldName] = this.makeTypedJsonFilterSchema(contextModel, fieldDef).optional(); } else { // enum, array, primitives const enumDef = getEnum(this.schema, fieldDef.type); if (enumDef) { - fieldSchemas[fieldName] = this.makeEnumFilterSchema( - fieldDef.type, - !!fieldDef.optional, - false, - !!fieldDef.array, - ).optional(); + fieldSchemas[fieldName] = this.makeEnumFilterSchema(contextModel, fieldDef, false).optional(); } else if (fieldDef.array) { - fieldSchemas[fieldName] = this.makeArrayFilterSchema(fieldDef.type as BuiltinType).optional(); + fieldSchemas[fieldName] = this.makeArrayFilterSchema(contextModel, fieldDef).optional(); } else { fieldSchemas[fieldName] = this.makePrimitiveFilterSchema( - fieldDef.type as BuiltinType, - !!fieldDef.optional, + contextModel, + fieldDef, false, ).optional(); } @@ -739,7 +752,9 @@ export class InputValidator { candidates.push(z.strictObject(fieldSchemas)); } - const recursiveSchema = z.lazy(() => this.makeTypedJsonFilterSchema(type, optional, false)).optional(); + const recursiveSchema = z + .lazy(() => this.makeTypedJsonFilterSchema(contextModel, { name: field, type, optional, array: false })) + .optional(); if (array) { // array filter candidates.push( @@ -760,7 +775,7 @@ export class InputValidator { } // plain json filter - candidates.push(this.makeJsonFilterSchema(optional)); + candidates.push(this.makeJsonFilterSchema(contextModel, field, optional)); if (optional) { // allow null as well @@ -776,49 +791,86 @@ export class InputValidator { } @cache() - private makeEnumFilterSchema(enumName: string, optional: boolean, withAggregations: boolean, array: boolean) { + private makeEnumFilterSchema( + model: string | undefined, + fieldInfo: FieldInfo, + withAggregations: boolean, + ignoreSlicing: boolean = false, + ) { + const enumName = fieldInfo.type; + const optional = !!fieldInfo.optional; + const array = !!fieldInfo.array; + const enumDef = getEnum(this.schema, enumName); invariant(enumDef, `Enum "${enumName}" not found in schema`); const baseSchema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); if (array) { - return this.internalMakeArrayFilterSchema(baseSchema); + return this.internalMakeArrayFilterSchema(model, fieldInfo.name, baseSchema); } + const allowedFilterKinds = ignoreSlicing ? undefined : this.getEffectiveFilterKinds(model, fieldInfo.name); const components = this.makeCommonPrimitiveFilterComponents( baseSchema, optional, - () => z.lazy(() => this.makeEnumFilterSchema(enumName, optional, withAggregations, array)), + () => z.lazy(() => this.makeEnumFilterSchema(model, fieldInfo, withAggregations)), ['equals', 'in', 'notIn', 'not'], withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, ); - return z.union([this.nullableIf(baseSchema, optional), z.strictObject(components)]); + + return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); } @cache() - private makeArrayFilterSchema(type: BuiltinType) { - return this.internalMakeArrayFilterSchema(this.makeScalarSchema(type)); + private makeArrayFilterSchema(model: string | undefined, fieldInfo: FieldInfo) { + return this.internalMakeArrayFilterSchema( + model, + fieldInfo.name, + this.makeScalarSchema(fieldInfo.type as BuiltinType), + ); } - private internalMakeArrayFilterSchema(elementSchema: ZodType) { - return z.strictObject({ + private internalMakeArrayFilterSchema(contextModel: string | undefined, field: string, elementSchema: ZodType) { + const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field); + const operators = { equals: elementSchema.array().optional(), has: elementSchema.optional(), hasEvery: elementSchema.array().optional(), hasSome: elementSchema.array().optional(), isEmpty: z.boolean().optional(), - }); + }; + + // Filter operators based on allowed filter kinds + const filteredOperators = this.trimFilterOperators(operators, allowedFilterKinds); + + return z.strictObject(filteredOperators); } @cache() - private makePrimitiveFilterSchema(type: BuiltinType, optional: boolean, withAggregations: boolean) { + private makePrimitiveFilterSchema( + contextModel: string | undefined, + fieldInfo: FieldInfo, + withAggregations: boolean, + ignoreSlicing = false, + ) { + const allowedFilterKinds = ignoreSlicing + ? undefined + : this.getEffectiveFilterKinds(contextModel, fieldInfo.name); + const type = fieldInfo.type as BuiltinType; + const optional = !!fieldInfo.optional; return match(type) - .with('String', () => this.makeStringFilterSchema(optional, withAggregations)) + .with('String', () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)) .with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) => - this.makeNumberFilterSchema(this.makeScalarSchema(type), optional, withAggregations), + this.makeNumberFilterSchema( + this.makeScalarSchema(type), + optional, + withAggregations, + allowedFilterKinds, + ), ) - .with('Boolean', () => this.makeBooleanFilterSchema(optional, withAggregations)) - .with('DateTime', () => this.makeDateTimeFilterSchema(optional, withAggregations)) - .with('Bytes', () => this.makeBytesFilterSchema(optional, withAggregations)) - .with('Json', () => this.makeJsonFilterSchema(optional)) + .with('Boolean', () => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with('DateTime', () => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with('Bytes', () => this.makeBytesFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with('Json', () => this.makeJsonFilterSchema(contextModel, fieldInfo.name, optional)) .with('Unsupported', () => z.never()) .exhaustive(); } @@ -851,7 +903,15 @@ export class InputValidator { } @cache() - private makeJsonFilterSchema(optional: boolean) { + private makeJsonFilterSchema(contextModel: string | undefined, field: string, optional: boolean) { + const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field); + + // Check if Json filter kind is allowed + if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) { + // Return a never schema if Json filters are not allowed + return z.never(); + } + const valueSchema = this.makeJsonValueSchema(optional, true); return z.strictObject({ path: z.string().optional(), @@ -868,29 +928,44 @@ export class InputValidator { } @cache() - private makeDateTimeFilterSchema(optional: boolean, withAggregations: boolean): ZodType { + private makeDateTimeFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { return this.makeCommonPrimitiveFilterSchema( z.union([z.iso.datetime(), z.date()]), optional, - () => z.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations)), + () => z.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)), withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, ); } @cache() - private makeBooleanFilterSchema(optional: boolean, withAggregations: boolean): ZodType { + private makeBooleanFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { const components = this.makeCommonPrimitiveFilterComponents( z.boolean(), optional, - () => z.lazy(() => this.makeBooleanFilterSchema(optional, withAggregations)), + () => z.lazy(() => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)), ['equals', 'not'], withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, ); - return z.union([this.nullableIf(z.boolean(), optional), z.strictObject(components)]); + + return this.createUnionFilterSchema(z.boolean(), optional, components, allowedFilterKinds); } @cache() - private makeBytesFilterSchema(optional: boolean, withAggregations: boolean): ZodType { + private makeBytesFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { const baseSchema = z.instanceof(Uint8Array); const components = this.makeCommonPrimitiveFilterComponents( baseSchema, @@ -898,8 +973,10 @@ export class InputValidator { () => z.instanceof(Uint8Array), ['equals', 'in', 'notIn', 'not'], withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, ); - return z.union([this.nullableIf(baseSchema, optional), z.strictObject(components)]); + + return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); } private makeCommonPrimitiveFilterComponents( @@ -908,12 +985,12 @@ export class InputValidator { makeThis: () => ZodType, supportedOperators: string[] | undefined = undefined, withAggregations: Array<'_count' | '_avg' | '_sum' | '_min' | '_max'> | undefined = undefined, + allowedFilterKinds: string[] | undefined = undefined, ) { const commonAggSchema = () => - this.makeCommonPrimitiveFilterSchema(baseSchema, false, makeThis, undefined).optional(); + this.makeCommonPrimitiveFilterSchema(baseSchema, false, makeThis, undefined, allowedFilterKinds).optional(); let result = { equals: this.nullableIf(baseSchema.optional(), optional), - notEquals: this.nullableIf(baseSchema.optional(), optional), in: baseSchema.array().optional(), notIn: baseSchema.array().optional(), lt: baseSchema.optional(), @@ -923,7 +1000,7 @@ export class InputValidator { between: baseSchema.array().length(2).optional(), not: makeThis().optional(), ...(withAggregations?.includes('_count') - ? { _count: this.makeNumberFilterSchema(z.number().int(), false, false).optional() } + ? { _count: this.makeNumberFilterSchema(z.number().int(), false, false, undefined).optional() } : {}), ...(withAggregations?.includes('_avg') ? { _avg: commonAggSchema() } : {}), ...(withAggregations?.includes('_sum') ? { _sum: commonAggSchema() } : {}), @@ -934,6 +1011,10 @@ export class InputValidator { const keys = [...supportedOperators, ...(withAggregations ?? [])]; result = extractFields(result, keys) as typeof result; } + + // Filter operators based on allowed filter kinds + result = this.trimFilterOperators(result, allowedFilterKinds) as typeof result; + return result; } @@ -941,46 +1022,70 @@ export class InputValidator { baseSchema: ZodType, optional: boolean, makeThis: () => ZodType, - withAggregations: Array | undefined = undefined, + withAggregations: Array | undefined = undefined, + allowedFilterKinds: string[] | undefined = undefined, ): z.ZodType { - return z.union([ - this.nullableIf(baseSchema, optional), - z.strictObject( - this.makeCommonPrimitiveFilterComponents(baseSchema, optional, makeThis, undefined, withAggregations), - ), - ]); + const components = this.makeCommonPrimitiveFilterComponents( + baseSchema, + optional, + makeThis, + undefined, + withAggregations, + allowedFilterKinds, + ); + + return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); } - private makeNumberFilterSchema(baseSchema: ZodType, optional: boolean, withAggregations: boolean): ZodType { + private makeNumberFilterSchema( + baseSchema: ZodType, + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { return this.makeCommonPrimitiveFilterSchema( baseSchema, optional, - () => z.lazy(() => this.makeNumberFilterSchema(baseSchema, optional, withAggregations)), + () => z.lazy(() => this.makeNumberFilterSchema(baseSchema, optional, withAggregations, allowedFilterKinds)), withAggregations ? ['_count', '_avg', '_sum', '_min', '_max'] : undefined, + allowedFilterKinds, ); } - private makeStringFilterSchema(optional: boolean, withAggregations: boolean): ZodType { - return z.union([ - this.nullableIf(z.string(), optional), - z.strictObject({ - ...this.makeCommonPrimitiveFilterComponents( - z.string(), - optional, - () => z.lazy(() => this.makeStringFilterSchema(optional, withAggregations)), - undefined, - withAggregations ? ['_count', '_min', '_max'] : undefined, - ), - startsWith: z.string().optional(), - endsWith: z.string().optional(), - contains: z.string().optional(), - ...(this.providerSupportsCaseSensitivity - ? { - mode: this.makeStringModeSchema().optional(), - } - : {}), - }), - ]); + private makeStringFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { + const baseComponents = this.makeCommonPrimitiveFilterComponents( + z.string(), + optional, + () => z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)), + undefined, + withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + + const stringSpecificOperators = { + startsWith: z.string().optional(), + endsWith: z.string().optional(), + contains: z.string().optional(), + ...(this.providerSupportsCaseSensitivity + ? { + mode: this.makeStringModeSchema().optional(), + } + : {}), + }; + + // Filter string-specific operators based on allowed filter kinds + const filteredStringOperators = this.trimFilterOperators(stringSpecificOperators, allowedFilterKinds); + + const allComponents = { + ...baseComponents, + ...filteredStringOperators, + }; + + return this.createUnionFilterSchema(z.string(), optional, allComponents, allowedFilterKinds); } private makeStringModeSchema() { @@ -994,7 +1099,10 @@ export class InputValidator { for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { - fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + // Check if the target model is allowed by slicing configuration + if (this.isModelAllowed(fieldDef.type)) { + fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + } } else { fields[field] = z.boolean().optional(); } @@ -1109,7 +1217,10 @@ export class InputValidator { for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { - fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + // Check if the target model is allowed by slicing configuration + if (this.isModelAllowed(fieldDef.type)) { + fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + } } } @@ -1249,6 +1360,10 @@ export class InputValidator { if (withoutRelationFields) { return; } + // Check if the target model is allowed by slicing configuration + if (!this.isModelAllowed(fieldDef.type)) { + return; + } const excludeFields: string[] = []; const oppositeField = fieldDef.relation.opposite; if (oppositeField) { @@ -1562,6 +1677,10 @@ export class InputValidator { if (withoutRelationFields) { return; } + // Check if the target model is allowed by slicing configuration + if (!this.isModelAllowed(fieldDef.type)) { + return; + } const excludeFields: string[] = []; const oppositeField = fieldDef.relation.opposite; if (oppositeField) { @@ -1801,7 +1920,7 @@ export class InputValidator { const bys = typeof value.by === 'string' ? [value.by] : value.by; if (value.having && typeof value.having === 'object') { for (const [key, val] of Object.entries(value.having)) { - if (AGGREGATE_OPERATORS.includes(key as any)) { + if (AggregateOperators.includes(key as any)) { continue; } if (bys.includes(key)) { @@ -1829,7 +1948,7 @@ export class InputValidator { if ( value.orderBy && Object.keys(value.orderBy) - .filter((f) => !AGGREGATE_OPERATORS.includes(f as AGGREGATE_OPERATORS)) + .filter((f) => !AggregateOperators.includes(f as AggregateOperators)) .some((key) => !bys.includes(key)) ) { return false; @@ -1843,7 +1962,7 @@ export class InputValidator { private onlyAggregationFields(val: object) { for (const [key, value] of Object.entries(val)) { - if (AGGREGATE_OPERATORS.includes(key as any)) { + if (AggregateOperators.includes(key as any)) { // aggregation field continue; } @@ -1965,5 +2084,171 @@ export class InputValidator { return this.schema.provider.type === 'postgresql'; } + /** + * Gets the effective set of allowed FilterKind values for a specific model and field. + * Respects the precedence: model[field] > model.$all > $all[field] > $all.$all. + */ + private getEffectiveFilterKinds(model: string | undefined, field: string): string[] | undefined { + if (!model) { + // no restrictions + return undefined; + } + + const slicing = this.options.slicing; + if (!slicing?.models) { + // no slicing or no model-specific slicing, no restrictions + return undefined; + } + + // A string-indexed view of slicing.models that avoids unsafe 'as any' while still + // allowing runtime access by model name. The value shape matches FieldSlicingOptions. + type FieldConfig = { includedFilterKinds?: readonly string[]; excludedFilterKinds?: readonly string[] }; + type FieldsRecord = { $all?: FieldConfig } & Record; + type ModelConfig = { fields?: FieldsRecord }; + const modelsRecord = slicing.models as Record; + + // Check field-level settings for the specific model + const modelConfig = modelsRecord[lowerCaseFirst(model)]; + if (modelConfig?.fields) { + const fieldConfig = modelConfig.fields[field]; + if (fieldConfig) { + return this.computeFilterKinds(fieldConfig.includedFilterKinds, fieldConfig.excludedFilterKinds); + } + + // Fallback to field-level $all for the specific model + const allFieldsConfig = modelConfig.fields['$all']; + if (allFieldsConfig) { + return this.computeFilterKinds( + allFieldsConfig.includedFilterKinds, + allFieldsConfig.excludedFilterKinds, + ); + } + } + + // Fallback to model-level $all + const allModelsConfig = modelsRecord['$all']; + if (allModelsConfig?.fields) { + // Check specific field in $all model config before falling back to $all.$all + const allModelsFieldConfig = allModelsConfig.fields[field]; + if (allModelsFieldConfig) { + return this.computeFilterKinds( + allModelsFieldConfig.includedFilterKinds, + allModelsFieldConfig.excludedFilterKinds, + ); + } + + // Fallback to $all.$all + const allModelsAllFieldsConfig = allModelsConfig.fields['$all']; + if (allModelsAllFieldsConfig) { + return this.computeFilterKinds( + allModelsAllFieldsConfig.includedFilterKinds, + allModelsAllFieldsConfig.excludedFilterKinds, + ); + } + } + + return undefined; // No restrictions + } + + /** + * Computes the effective set of filter kinds based on inclusion and exclusion lists. + */ + private computeFilterKinds(included: readonly string[] | undefined, excluded: readonly string[] | undefined) { + let result: string[] | undefined; + + if (included !== undefined) { + // Start with the included set + result = [...included]; + } + + if (excluded !== undefined) { + if (!result) { + // If no inclusion list, start with all filter kinds + result = [...this.allFilterKinds]; + } + // Remove excluded kinds + for (const kind of excluded) { + result = result.filter((k) => k !== kind); + } + } + + return result; + } + + /** + * Filters operators based on allowed filter kinds. + */ + private trimFilterOperators>( + operators: T, + allowedKinds: string[] | undefined, + ): Partial { + if (!allowedKinds) { + return operators; // No restrictions + } + + return Object.fromEntries( + Object.entries(operators).filter(([key, _]) => { + return ( + !(key in FILTER_PROPERTY_TO_KIND) || + allowedKinds.includes(FILTER_PROPERTY_TO_KIND[key as keyof typeof FILTER_PROPERTY_TO_KIND]) + ); + }), + ) as Partial; + } + + private createUnionFilterSchema( + valueSchema: ZodType, + optional: boolean, + components: Record, + allowedFilterKinds: string[] | undefined, + ) { + // If all filter operators are excluded + if (Object.keys(components).length === 0) { + // if equality filters are allowed, allow direct value + if (!allowedFilterKinds || allowedFilterKinds.includes('Equality')) { + return this.nullableIf(valueSchema, optional); + } + // otherwise nothing is allowed + return z.never(); + } + + if (!allowedFilterKinds || allowedFilterKinds.includes('Equality')) { + // direct value or filter operators + return z.union([this.nullableIf(valueSchema, optional), z.strictObject(components)]); + } else { + // filter operators + return z.strictObject(components); + } + } + + /** + * Checks if a model is included in the slicing configuration. + * Returns true if the model is allowed, false if it's excluded. + */ + private isModelAllowed(targetModel: string): boolean { + const slicing = this.options.slicing; + if (!slicing) { + return true; // No slicing, all models allowed + } + + const { includedModels, excludedModels } = slicing; + + // If includedModels is specified, only those models are allowed + if (includedModels !== undefined) { + if (!includedModels.includes(targetModel as any)) { + return false; + } + } + + // If excludedModels is specified, those models are not allowed + if (excludedModels !== undefined) { + if (excludedModels.includes(targetModel as any)) { + return false; + } + } + + return true; + } + // #endregion } diff --git a/packages/orm/src/client/index.ts b/packages/orm/src/client/index.ts index 00bbf1b6d..25015c353 100644 --- a/packages/orm/src/client/index.ts +++ b/packages/orm/src/client/index.ts @@ -20,3 +20,4 @@ export * from './plugin'; export type { ZenStackPromise } from './promise'; export type { ToKysely } from './query-builder'; export * as QueryUtils from './query-utils'; +export type * from './type-utils'; diff --git a/packages/orm/src/client/options.ts b/packages/orm/src/client/options.ts index 6439e3996..80aa82261 100644 --- a/packages/orm/src/client/options.ts +++ b/packages/orm/src/client/options.ts @@ -1,9 +1,11 @@ import type { Dialect, Expression, ExpressionBuilder, KyselyConfig } from 'kysely'; import type { GetModel, GetModelFields, GetModels, ProcedureDef, ScalarFields, SchemaDef } from '../schema'; import type { PrependParameter } from '../utils/type-utils'; +import type { FilterPropertyToKind } from './constants'; import type { ClientContract, CRUD_EXT } from './contract'; import type { GetProcedureNames, ProcedureHandlerFunc } from './crud-types'; import type { BaseCrudDialect } from './crud/dialects/base-dialect'; +import type { AllCrudOperations } from './crud/operations/base'; import type { AnyPlugin } from './plugin'; import type { ToKyselySchema } from './query-builder'; @@ -40,10 +42,125 @@ export type ZModelFunction = ( context: ZModelFunctionContext, ) => Expression; +/** + * Options for slicing ORM client's capabilities by including/excluding certain models, operations, + * filters, etc. + */ +export type SlicingOptions = { + /** + * Models to include in the client. If not specified, all models are included by default. + */ + includedModels?: readonly GetModels[]; + + /** + * Models to exclude from the client. Exclusion takes precedence over inclusion. + */ + excludedModels?: readonly GetModels[]; + + /** + * Model slicing options. + */ + models?: { + /** + * Model-specific slicing options. + */ + [Model in GetModels as Uncapitalize]?: ModelSlicingOptions; + } & { + /** + * Slicing options that apply to all models. Model-specific options will override these general + * options if both are specified. + */ + $all?: ModelSlicingOptions>; + }; + + /** + * Procedures to include in the client. If not specified, all procedures are included by default. + */ + includedProcedures?: readonly GetProcedureNames[]; + + /** + * Procedures to exclude from the client. Exclusion takes precedence over inclusion. + */ + excludedProcedures?: readonly GetProcedureNames[]; +}; + +/** + * Kinds of filter operations. + */ +export type FilterKind = FilterPropertyToKind[keyof FilterPropertyToKind]; + +/** + * Model slicing options. + */ +export type ModelSlicingOptions> = { + /** + * ORM query operations to include for the model. If not specified, all operations are included + * by default. + */ + includedOperations?: readonly AllCrudOperations[]; + + /** + * ORM query operations to exclude for the model. Exclusion takes precedence over inclusion. + */ + excludedOperations?: readonly AllCrudOperations[]; + + /** + * Field-level slicing options. + */ + fields?: { + /** + * Field-specific slicing options. + */ + [Field in GetModelFields]?: FieldSlicingOptions; + } & { + /** + * Field slicing options that apply to all fields. Field-specific options will override these + * general options if both are specified. + */ + $all?: FieldSlicingOptions; + }; +}; + +/** + * Field slicing options. + */ +type FieldSlicingOptions = { + /** + * Filter kinds to include for the field. If not specified, all filter kinds are included by default. + */ + includedFilterKinds?: readonly FilterKind[]; + + /** + * Filter kinds to exclude for the field. Exclusion takes precedence over inclusion. + */ + excludedFilterKinds?: readonly FilterKind[]; +}; + +/** + * Partial ORM client options that defines customizable behaviors. + */ +export type QueryOptions = { + /** + * Options for omitting fields in ORM query results. + */ + omit?: OmitConfig; + + /** + * Whether to allow overriding omit settings at query time. Defaults to `true`. When set to `false`, a + * query-time `omit` clause that sets the field to `false` (not omitting) will trigger a validation error. + */ + allowQueryTimeOmitOverride?: boolean; + + /** + * Options for slicing ORM client's capabilities by including/excluding certain models, operations, filters, etc. + */ + slicing?: SlicingOptions; +}; + /** * ZenStack client options. */ -export type ClientOptions = { +export type ClientOptions = QueryOptions & { /** * Kysely dialect. */ @@ -81,26 +198,14 @@ export type ClientOptions = { * `@@validate`, etc. Defaults to `true`. */ validateInput?: boolean; - - /** - * Options for omitting fields in ORM query results. - */ - omit?: OmitConfig; - - /** - * Whether to allow overriding omit settings at query time. Defaults to `true`. When set to - * `false`, an `omit` clause that sets field to `false` (not omitting) will trigger a validation - * error. - */ - allowQueryTimeOmitOverride?: boolean; } & (HasComputedFields extends true - ? { - /** - * Computed field definitions. - */ - computedFields: ComputedFieldsOptions; - } - : {}) & + ? { + /** + * Computed field definitions. + */ + computedFields: ComputedFieldsOptions; + } + : {}) & (HasProcedures extends true ? { /** @@ -146,11 +251,6 @@ export type HasProcedures = Schema extends { : false; /** - * Subset of client options relevant to query operations. - */ -export type QueryOptions = Pick, 'omit'>; - -/** - * Extract QueryOptions from ClientOptions + * Extracts QueryOptions from an object with '$options' property. */ -export type ToQueryOptions> = Pick; +export type GetQueryOptions = T['$options']; diff --git a/packages/orm/src/client/query-utils.ts b/packages/orm/src/client/query-utils.ts index 66e41c404..fb9c39bea 100644 --- a/packages/orm/src/client/query-utils.ts +++ b/packages/orm/src/client/query-utils.ts @@ -11,7 +11,7 @@ import { import { match } from 'ts-pattern'; import { ExpressionUtils, type FieldDef, type GetModels, type ModelDef, type SchemaDef } from '../schema'; import { extractFields } from '../utils/object-utils'; -import type { AGGREGATE_OPERATORS } from './constants'; +import type { AggregateOperators } from './constants'; import { createInternalError } from './errors'; export function hasModel(schema: SchemaDef, model: string) { @@ -386,7 +386,7 @@ export function getDelegateDescendantModels( return [...collected]; } -export function aggregate(eb: ExpressionBuilder, expr: Expression, op: AGGREGATE_OPERATORS) { +export function aggregate(eb: ExpressionBuilder, expr: Expression, op: AggregateOperators) { return match(op) .with('_count', () => eb.fn.count(expr)) .with('_sum', () => eb.fn.sum(expr)) diff --git a/packages/orm/src/client/type-utils.ts b/packages/orm/src/client/type-utils.ts new file mode 100644 index 000000000..3a4589f71 --- /dev/null +++ b/packages/orm/src/client/type-utils.ts @@ -0,0 +1,262 @@ +import type { GetModels, SchemaDef } from '@zenstackhq/schema'; +import type { GetProcedureNames } from './crud-types'; +import type { AllCrudOperations } from './crud/operations/base'; +import type { FilterKind, QueryOptions, SlicingOptions } from './options'; + +type IsNever = [T] extends [never] ? true : false; + +// #region Model slicing + +/** + * Filters models based on slicing configuration. + */ +export type GetSlicedModels< + Schema extends SchemaDef, + Options extends QueryOptions, +> = Options['slicing'] extends infer S + ? S extends SlicingOptions + ? S['includedModels'] extends readonly GetModels[] + ? // includedModels is specified, start with only those + Exclude< + Extract>, + S['excludedModels'] extends readonly GetModels[] ? S['excludedModels'][number] : never + > + : // includedModels not specified, start with all models + Exclude< + GetModels, + S['excludedModels'] extends readonly GetModels[] ? S['excludedModels'][number] : never + > + : // No slicing config, include all models + GetModels + : GetModels; + +// #endregion + +// #region Operation slicing + +/** + * Filters query operations based on slicing configuration for a specific model. + */ +export type GetSlicedOperations< + Schema extends SchemaDef, + Model extends GetModels, + Options extends QueryOptions, +> = Options['slicing'] extends infer Slicing + ? Slicing extends SlicingOptions + ? GetIncludedOperations extends infer IO + ? GetExcludedOperations extends infer EO + ? IO extends '_none_' + ? // special case for empty includeOperations array - exclude all operations + never + : IsNever extends false + ? // includedOperations is specified, use those minus any excludedOperations + Exclude + : // includedOperations not specified, use all operations minus any excludedOperations + Exclude + : AllCrudOperations + : AllCrudOperations + : AllCrudOperations + : AllCrudOperations; + +export type GetIncludedOperations< + Slicing extends SlicingOptions, + Model extends string, +> = 'models' extends keyof Slicing + ? Slicing extends { models: infer Config } + ? Uncapitalize extends keyof Config + ? 'includedOperations' extends keyof Config[Uncapitalize] + ? // 'includedOperations' is specified for the model + Config[Uncapitalize] extends { includedOperations: readonly [] } + ? // special marker for empty array (mute all) + '_none_' + : // use the specified includedOperations + Config[Uncapitalize] extends { includedOperations: readonly (infer IO)[] } + ? IO + : never + : // fallback to $all if 'includedOperations' not specified for the model + GetAllIncludedOperations + : // fallback to $all if model-specific config not found + GetAllIncludedOperations + : AllCrudOperations + : AllCrudOperations; + +export type GetAllIncludedOperations> = 'models' extends keyof Slicing + ? Slicing extends { models: infer Config } + ? '$all' extends keyof Config + ? Config['$all'] extends { includedOperations: readonly [] } + ? '_none_' + : Config['$all'] extends { includedOperations: readonly (infer IO)[] } + ? IO + : AllCrudOperations + : AllCrudOperations + : AllCrudOperations + : AllCrudOperations; + +type GetExcludedOperations, Model extends string> = 'models' extends keyof Slicing + ? Slicing extends { models: infer Config } + ? Uncapitalize extends keyof Config + ? Config[Uncapitalize] extends { excludedOperations: readonly (infer EO)[] } + ? EO + : // fallback to $all if 'excludedOperations' not specified for the model + GetAllExcludedOperations + : // fallback to $all if model-specific config not found + GetAllExcludedOperations + : never + : never; + +type GetAllExcludedOperations> = 'models' extends keyof Slicing + ? Slicing extends { models: infer M } + ? '$all' extends keyof M + ? M['$all'] extends { excludedOperations: readonly (infer EO)[] } + ? EO + : never + : never + : never + : never; + +// #endregion + +// #region Procedure slicing + +/** + * Filters procedures based on slicing configuration. + */ +export type GetSlicedProcedures< + Schema extends SchemaDef, + Options extends QueryOptions, +> = Options['slicing'] extends infer S + ? S extends SlicingOptions + ? S['includedProcedures'] extends readonly (infer IncludedProc)[] + ? // includedProcedures is specified, start with only those + Exclude< + Extract>, + S['excludedProcedures'] extends readonly (infer ExcludedProc)[] + ? Extract> + : never + > + : // includedProcedures not specified, start with all procedures + Exclude< + GetProcedureNames, + S['excludedProcedures'] extends readonly (infer ExcludedProc)[] + ? Extract> + : never + > + : // No slicing config, include all procedures + GetProcedureNames + : GetProcedureNames; + +// #endregion + +// #region Filter slicing + +/** + * Filters filter kinds for a specific field, considering field-level slicing configuration with $all fallback. + */ +export type GetSlicedFilterKindsForField< + Schema extends SchemaDef, + Model extends GetModels, + Field extends string, + Options extends QueryOptions, +> = Options extends { slicing: infer S } + ? S extends SlicingOptions + ? GetFieldIncludedFilterKinds extends infer IFK + ? GetFieldExcludedFilterKinds extends infer EFK + ? '_none_' extends IFK + ? // Empty includedFilterKinds array - exclude all + never + : IsNever extends true + ? // No field-level includedFilterKinds specified + IsNever extends true + ? // No field-level exclusions either - allow all + FilterKind + : // Field-level exclusions exist - exclude them from all filter kinds + Exclude + : // Field-level includedFilterKinds specified - use those and apply exclusions + Exclude + : FilterKind + : FilterKind + : FilterKind + : FilterKind; + +// Helper type to extract includedFilterKinds from a model config (handles both specific model and $all) +type GetIncludedFilterKindsFromModelConfig = ModelConfig extends { + includedFilterKinds: readonly []; +} + ? '_none_' + : 'fields' extends keyof ModelConfig + ? ModelConfig['fields'] extends infer FieldsConfig + ? // Check if specific field config exists + Field extends keyof FieldsConfig + ? 'includedFilterKinds' extends keyof FieldsConfig[Field] + ? // Field-specific includedFilterKinds + FieldsConfig[Field] extends { includedFilterKinds: readonly [] } + ? '_none_' + : FieldsConfig[Field] extends { includedFilterKinds: readonly (infer IFK)[] } + ? IFK + : never + : // No field-specific includedFilterKinds, try $all + GetAllFieldsIncludedFilterKinds + : // No field-specific config, try $all + GetAllFieldsIncludedFilterKinds + : never + : never; + +type GetFieldIncludedFilterKinds< + S extends SlicingOptions, + Model extends string, + Field extends string, +> = S extends { + models?: infer Config; +} + ? Uncapitalize extends keyof Config + ? GetIncludedFilterKindsFromModelConfig], Field> + : // Model not in config, fallback to $all + '$all' extends keyof Config + ? GetIncludedFilterKindsFromModelConfig + : never + : never; + +type GetAllFieldsIncludedFilterKinds = '$all' extends keyof FieldsConfig + ? FieldsConfig['$all'] extends { includedFilterKinds: readonly [] } + ? '_none_' + : FieldsConfig['$all'] extends { includedFilterKinds: readonly (infer IFK)[] } + ? IFK + : never + : never; + +// Helper type to extract excludedFilterKinds from a model config (handles both specific model and $all) +type GetExcludedFilterKindsFromModelConfig = 'fields' extends keyof ModelConfig + ? ModelConfig['fields'] extends infer FieldsConfig + ? // Check if specific field config exists + Field extends keyof FieldsConfig + ? FieldsConfig[Field] extends { excludedFilterKinds: readonly (infer EFK)[] } + ? EFK + : // No field-specific excludedFilterKinds, try $all + GetAllFieldsExcludedFilterKinds + : // No field-specific config, try $all + GetAllFieldsExcludedFilterKinds + : never + : never; + +type GetFieldExcludedFilterKinds< + S extends SlicingOptions, + Model extends string, + Field extends string, +> = S extends { + models?: infer Config; +} + ? Uncapitalize extends keyof Config + ? GetExcludedFilterKindsFromModelConfig], Field> + : // Model not in config, fallback to $all + '$all' extends keyof Config + ? GetExcludedFilterKindsFromModelConfig + : never + : never; + +type GetAllFieldsExcludedFilterKinds = '$all' extends keyof FieldsConfig + ? FieldsConfig['$all'] extends { excludedFilterKinds: readonly (infer EFK)[] } + ? EFK + : never + : never; + +// #endregion diff --git a/packages/zod/package.json b/packages/zod/package.json index 4853c6295..a146e20e9 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,10 +1,8 @@ { "name": "@zenstackhq/zod", "version": "3.3.3", - "description": "", + "description": "ZenStack Zod integration", "type": "module", - "main": "index.js", - "private": true, "scripts": { "build": "tsc --noEmit && tsup-node", "lint": "eslint src --ext ts" diff --git a/tests/e2e/orm/client-api/slicing.test.ts b/tests/e2e/orm/client-api/slicing.test.ts new file mode 100644 index 000000000..5ba5fcb35 --- /dev/null +++ b/tests/e2e/orm/client-api/slicing.test.ts @@ -0,0 +1,2001 @@ +import { AllReadOperations } from '@zenstackhq/orm'; +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; +import { schema } from '../schemas/basic/schema'; +import { schema as proceduresSchema } from '../schemas/procedures/schema'; + +describe('Query slicing tests', () => { + describe('Model inclusion/exclusion', () => { + it('includes all models when no slicing config', async () => { + const db = await createTestClient(schema); + + // All models should be accessible + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + expect(db.comment).toBeDefined(); + expect(db.profile).toBeDefined(); + expect(db.plain).toBeDefined(); + }); + + it('includes only specified models with includedModels', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // included models should be accessible + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + + // @ts-expect-error - Profile model should not be accessible + expect(db.profile).toBeUndefined(); + // @ts-expect-error - Plain model should not be accessible + expect(db.plain).toBeUndefined(); + }); + + it('excludes specified models with excludedModels', async () => { + const options = { + slicing: { + excludedModels: ['Comment', 'Profile'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // non-excluded models should be accessible + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + expect(db.plain).toBeDefined(); + + // excluded models should not be accessible + // @ts-expect-error - Comment model should be excluded + expect(db.comment).toBeUndefined(); + // @ts-expect-error - Profile model should be excluded + expect(db.profile).toBeUndefined(); + }); + + it('applies both includedModels and excludedModels (exclusion after inclusion)', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post', 'Comment'] as const, + excludedModels: ['Comment'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // only User and Post should be accessible (Comment excluded after being included) + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + + // Comment should be excluded despite being in includedModels + // @ts-expect-error - Comment model should be excluded + expect(db.comment).toBeUndefined(); + + // Profile and Plain were never included + // @ts-expect-error - Profile model was not included + expect(db.profile).toBeUndefined(); + // @ts-expect-error - Plain model was not included + expect(db.plain).toBeUndefined(); + }); + + it('excludes all models when includedModels is empty array', async () => { + const options = { + slicing: { + includedModels: [] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // no models should be accessible with empty includedModels + // @ts-expect-error - User model should not be accessible + expect(db.user).toBeUndefined(); + // @ts-expect-error - Post model should not be accessible + expect(db.post).toBeUndefined(); + // @ts-expect-error - Comment model should not be accessible + expect(db.comment).toBeUndefined(); + // @ts-expect-error - Profile model should not be accessible + expect(db.profile).toBeUndefined(); + // @ts-expect-error - Plain model should not be accessible + expect(db.plain).toBeUndefined(); + }); + + it('has no effect when excludedModels is empty array', async () => { + const options = { + slicing: { + excludedModels: [] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // All models should be accessible (empty excludedModels has no effect) + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + expect(db.comment).toBeDefined(); + expect(db.profile).toBeDefined(); + expect(db.plain).toBeDefined(); + }); + + it('works with setOptions to change slicing at runtime', async () => { + const initialOptions = { + slicing: { + includedModels: ['User', 'Post'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, initialOptions); + + // Initially only User and Post are accessible + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + + // Change slicing options + const newOptions = { + ...initialOptions, + slicing: { + includedModels: ['Comment', 'Profile'] as const, + }, + } as const; + + const db2 = db.$setOptions(newOptions); + + // After setOptions, different models should be accessible + expect(db2.comment).toBeDefined(); + expect(db2.profile).toBeDefined(); + + // Original client should remain unchanged + expect(db.user).toBeDefined(); + expect(db.post).toBeDefined(); + }); + + it('prevents excluded models from being used in include clause', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post'] as const, + // excludedModels: ['Profile', 'Comment'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Profile is excluded, so including it should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - Profile model is excluded + include: { profile: true }, + }), + ).toBeRejectedByValidation(['"profile"', '"include"']); + + // Comment is excluded, so including it should cause type error + await expect( + db.post.findMany({ + // @ts-expect-error - Comment model is excluded + include: { comments: true }, + }), + ).toBeRejectedByValidation(['"comments"', '"include"']); + + // Non-excluded relations should work + const userWithPosts = await db.user.findMany({ + include: { posts: true }, + }); + expect(userWithPosts[0]!.posts).toBeDefined(); + }); + + it('prevents excluded models from being used in select clause', async () => { + const options = { + slicing: { + excludedModels: ['Profile', 'Comment'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Profile is excluded, so selecting it should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - Profile model is excluded + select: { id: true, profile: true }, + }), + ).toBeRejectedByValidation(['"profile"', '"select"']); + + // Comment is excluded, so selecting it should cause type error + await expect( + db.post.findMany({ + // @ts-expect-error - Comment model is excluded + select: { id: true, comments: true }, + }), + ).toBeRejectedByValidation(['"comments"', '"select"']); + + // Non-excluded relations should work in select + const userWithPosts = await db.user.findMany({ + select: { id: true, posts: true }, + }); + expect(userWithPosts[0]!.posts).toBeDefined(); + }); + + it('prevents models not in includedModels from being used in include clause', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Profile is not included, so including it should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - Profile model is not included + include: { profile: true }, + }), + ).toBeRejectedByValidation(['"profile"', '"include"']); + + // Comment is not included, so including it should cause type error + await expect( + db.post.findMany({ + // @ts-expect-error - Comment model is not included + include: { comments: true }, + }), + ).toBeRejectedByValidation(['"comments"', '"include"']); + + // User and Post are included, so relations between them should work + const userWithPosts = await db.user.findMany({ + include: { posts: true }, + }); + expect(userWithPosts[0]!.posts).toBeDefined(); + + const postWithAuthor = await db.post.findMany({ + include: { author: true }, + }); + expect(postWithAuthor[0]!.author).toBeDefined(); + }); + + it('prevents models not in includedModels from being used in select clause', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Profile is not included, so selecting it should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - Profile model is not included + select: { id: true, profile: true }, + }), + ).toBeRejectedByValidation(['"profile"', '"select"']); + + // Comment is not included, so selecting it should cause type error + await expect( + db.post.findMany({ + // @ts-expect-error - Comment model is not included + select: { id: true, comments: true }, + }), + ).toBeRejectedByValidation(['"comments"', '"select"']); + + // User and Post are included, so relations between them should work in select + const userWithPosts = await db.user.findMany({ + select: { id: true, posts: true }, + }); + expect(userWithPosts[0]!.posts).toBeDefined(); + + const postWithAuthor = await db.post.findMany({ + select: { id: true, author: true }, + }); + expect(postWithAuthor[0]!.author).toBeDefined(); + }); + + it('prevents excluded models from nested include clauses', async () => { + const options = { + slicing: { + excludedModels: ['Comment'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Comment is excluded, so including it in nested include should cause type error + await expect( + db.user.findMany({ + include: { + posts: { + // @ts-expect-error - Comment model is excluded + include: { comments: true }, + }, + }, + }), + ).toBeRejectedByValidation(['"comments"']); + + // User -> Post relation should work (Comment is excluded) + const userWithPosts = await db.user.findMany({ + include: { + posts: true, + }, + }); + expect(userWithPosts[0]!.posts).toBeDefined(); + }); + + it('prevents excluded models from nested select clauses', async () => { + const options = { + slicing: { + excludedModels: ['Comment'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Comment is excluded, so selecting it in nested select should cause type error + await expect( + db.user.findMany({ + select: { + id: true, + posts: { + // @ts-expect-error - Comment model is excluded + select: { id: true, comments: true }, + }, + }, + }), + ).toBeRejectedByValidation(['"comments"']); + + // User -> Post relation should work in nested select (Comment is excluded) + const userWithPosts = await db.user.findMany({ + select: { + id: true, + posts: { + select: { id: true, title: true }, + }, + }, + }); + expect(userWithPosts[0]!.posts).toBeDefined(); + }); + + it('prevents nested create on excluded models', async () => { + const options = { + slicing: { + excludedModels: ['Profile'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Cannot create user with nested profile (Profile is excluded) + await expect( + db.user.create({ + data: { + email: 'test@example.com', + // @ts-expect-error - Profile model is excluded + profile: { + create: { + bio: 'Test bio', + }, + }, + }, + }), + ).toBeRejectedByValidation(['"profile"']); + }); + + it('prevents nested update on excluded models', async () => { + const options = { + slicing: { + excludedModels: ['Profile'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + const user = await db.user.create({ data: { email: 'test@example.com' } }); + + // Cannot update user with nested profile operations (Profile is excluded) + await expect( + db.user.update({ + where: { id: user.id }, + data: { + // @ts-expect-error - Profile model is excluded + profile: { + create: { + bio: 'Test bio', + }, + }, + }, + }), + ).toBeRejectedByValidation(['"profile"']); + }); + + it('prevents nested upsert on excluded models', async () => { + const options = { + slicing: { + excludedModels: ['Comment'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Cannot update post with nested comment operations (Comment is excluded) + await expect( + db.post.update({ + where: { id: 'post-id' }, + data: { + // @ts-expect-error - Comment model is excluded + comments: { + upsert: { + where: { id: 'comment-id' }, + create: { content: 'New comment' }, + update: { content: 'Updated comment' }, + }, + }, + }, + }), + ).toBeRejectedByValidation(['"comments"']); + }); + + it('allows nested create on included models', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Can create user with nested posts (Post is included) + const user = await db.user.create({ + data: { + email: 'test@example.com', + posts: { + create: [ + { title: 'Post 1', content: 'Content 1' }, + { title: 'Post 2', content: 'Content 2' }, + ], + }, + }, + include: { posts: true }, + }); + + expect(user.posts).toHaveLength(2); + expect(user.posts[0]!.title).toBe('Post 1'); + }); + + it('allows nested update on included models', async () => { + const options = { + slicing: { + includedModels: ['User', 'Post'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Create user with post + const user = await db.user.create({ + data: { + email: 'test@example.com', + posts: { + create: { title: 'Post 1', content: 'Content 1' }, + }, + }, + include: { posts: true }, + }); + + const postId = user.posts[0]!.id; + + // Can update user with nested post updates (Post is included) + const updated = await db.user.update({ + where: { id: user.id }, + data: { + posts: { + update: { + where: { id: postId }, + data: { title: 'Updated Post' }, + }, + }, + }, + include: { posts: true }, + }); + + expect(updated.posts[0]!.title).toBe('Updated Post'); + }); + }); + + describe('Operation inclusion/exclusion', () => { + it('includes only specified operations with includedOperations', async () => { + const options = { + slicing: { + models: { + user: { + includedOperations: ['findMany', 'create'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Included operations should be accessible + expect(db.user.findMany).toBeDefined(); + expect(db.user.create).toBeDefined(); + + // Excluded operations should not be accessible + // @ts-expect-error - findUnique should not be accessible + expect(db.user.findUnique).toBeUndefined(); + // @ts-expect-error - update should not be accessible + expect(db.user.update).toBeUndefined(); + // @ts-expect-error - delete should not be accessible + expect(db.user.delete).toBeUndefined(); + }); + + it('excludes specified operations with excludedOperations', async () => { + const options = { + slicing: { + models: { + user: { + excludedOperations: ['delete', 'deleteMany', 'update', 'updateMany'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Non-excluded operations should be accessible + expect(db.user.findMany).toBeDefined(); + expect(db.user.create).toBeDefined(); + expect(db.user.count).toBeDefined(); + + // Excluded operations should not be accessible + // @ts-expect-error - delete should be excluded + expect(db.user.delete).toBeUndefined(); + // @ts-expect-error - deleteMany should be excluded + expect(db.user.deleteMany).toBeUndefined(); + // @ts-expect-error - update should be excluded + expect(db.user.update).toBeUndefined(); + // @ts-expect-error - updateMany should be excluded + expect(db.user.updateMany).toBeUndefined(); + }); + + it('applies both includedOperations and excludedOperations', async () => { + const options = { + slicing: { + models: { + user: { + includedOperations: ['findMany', 'findUnique', 'create', 'update'] as const, + excludedOperations: ['update'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Only findMany, findUnique, and create should be accessible + expect(db.user.findMany).toBeDefined(); + expect(db.user.findUnique).toBeDefined(); + expect(db.user.create).toBeDefined(); + + // update should be excluded despite being in includedOperations + // @ts-expect-error - update should be excluded + expect(db.user.update).toBeUndefined(); + + // delete was never included + // @ts-expect-error - delete was not included + expect(db.user.delete).toBeUndefined(); + }); + + it('restricts operations for a single model without affecting others', async () => { + const options = { + slicing: { + models: { + user: { + includedOperations: ['findMany', 'create'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // User should have restricted operations + expect(db.user.findMany).toBeDefined(); + expect(db.user.create).toBeDefined(); + // @ts-expect-error - update not included for User + expect(db.user.update).toBeUndefined(); + + // Post should have all operations (no restrictions) + expect(db.post.findMany).toBeDefined(); + expect(db.post.create).toBeDefined(); + expect(db.post.update).toBeDefined(); + expect(db.post.delete).toBeDefined(); + }); + + it('creates read-only model with only read operations', async () => { + const options = { + slicing: { + models: { + user: { + includedOperations: AllReadOperations, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // Read operations should be accessible + expect(db.user.findMany).toBeDefined(); + expect(db.user.findUnique).toBeDefined(); + expect(db.user.findFirst).toBeDefined(); + expect(db.user.count).toBeDefined(); + expect(db.user.exists).toBeDefined(); + + // Write operations should not be accessible + // @ts-expect-error - create should not be accessible + expect(db.user.create).toBeUndefined(); + // @ts-expect-error - update should not be accessible + expect(db.user.update).toBeUndefined(); + // @ts-expect-error - delete should not be accessible + expect(db.user.delete).toBeUndefined(); + }); + + it('excludes all operations when includedOperations is empty', async () => { + const options = { + slicing: { + models: { + user: { + includedOperations: [] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // No operations should be accessible + // @ts-expect-error - findMany should not be accessible + expect(db.user.findMany).toBeUndefined(); + // @ts-expect-error - create should not be accessible + expect(db.user.create).toBeUndefined(); + // @ts-expect-error - update should not be accessible + expect(db.user.update).toBeUndefined(); + }); + + it('applies $all slicing to all models when no model-specific config', async () => { + const options = { + slicing: { + models: { + $all: { + includedOperations: ['findMany', 'count'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // All models should have only findMany and count + expect(db.user.findMany).toBeDefined(); + expect(db.user.count).toBeDefined(); + // @ts-expect-error - create should not be accessible + expect(db.user.create).toBeUndefined(); + + expect(db.post.findMany).toBeDefined(); + expect(db.post.count).toBeDefined(); + // @ts-expect-error - update should not be accessible + expect(db.post.update).toBeUndefined(); + }); + + it('model-specific slicing overrides $all slicing', async () => { + const options = { + slicing: { + models: { + $all: { + includedOperations: ['findMany', 'count'] as const, + }, + user: { + includedOperations: ['findMany', 'create', 'update'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // User should have model-specific operations + expect(db.user.findMany).toBeDefined(); + expect(db.user.create).toBeDefined(); + expect(db.user.update).toBeDefined(); + // @ts-expect-error - count is in $all but User overrides + expect(db.user.count).toBeUndefined(); + // @ts-expect-error - delete not in User's includedOperations + expect(db.user.delete).toBeUndefined(); + + // Post should have $all operations + expect(db.post.findMany).toBeDefined(); + expect(db.post.count).toBeDefined(); + // @ts-expect-error - create not in $all + expect(db.post.create).toBeUndefined(); + }); + + it('uses $all excludedOperations as fallback', async () => { + const options = { + slicing: { + models: { + $all: { + excludedOperations: ['delete', 'deleteMany'] as const, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + // All models should exclude delete and deleteMany + expect(db.user.findMany).toBeDefined(); + expect(db.user.create).toBeDefined(); + // @ts-expect-error - delete should be excluded + expect(db.user.delete).toBeUndefined(); + // @ts-expect-error - deleteMany should be excluded + expect(db.user.deleteMany).toBeUndefined(); + + expect(db.post.update).toBeDefined(); + // @ts-expect-error - delete should be excluded + expect(db.post.delete).toBeUndefined(); + }); + }); + + describe('Procedure inclusion/exclusion', () => { + // Mock procedure handlers for testing (simplified versions) + const mockProcedures = { + getUser: () => ({ id: 1, name: 'test', role: 'USER' as const }), + listUsers: () => [], + signUp: () => ({ id: 1, name: 'test', role: 'USER' as const }), + setAdmin: () => undefined, + getOverview: () => ({ userIds: [], total: 0, roles: ['USER' as const], meta: null }), + createMultiple: () => [], + }; + + it('includes all procedures when no slicing config', async () => { + const db = await createTestClient(proceduresSchema, { + procedures: mockProcedures as any, + }); + + // All procedures should be accessible + expect(db.$procs.getUser).toBeDefined(); + expect(db.$procs.listUsers).toBeDefined(); + expect(db.$procs.signUp).toBeDefined(); + expect(db.$procs.setAdmin).toBeDefined(); + expect(db.$procs.getOverview).toBeDefined(); + expect(db.$procs.createMultiple).toBeDefined(); + }); + + it('includes only specified procedures with includedProcedures', async () => { + const options = { + procedures: mockProcedures as any, + slicing: { + includedProcedures: ['getUser', 'listUsers'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(proceduresSchema, options); + + // Included procedures should be accessible + expect(db.$procs.getUser).toBeDefined(); + expect(db.$procs.listUsers).toBeDefined(); + + // Non-included procedures should not be accessible + // @ts-expect-error - signUp should not be accessible + expect(db.$procs.signUp).toBeUndefined(); + // @ts-expect-error - setAdmin should not be accessible + expect(db.$procs.setAdmin).toBeUndefined(); + // @ts-expect-error - getOverview should not be accessible + expect(db.$procs.getOverview).toBeUndefined(); + // @ts-expect-error - createMultiple should not be accessible + expect(db.$procs.createMultiple).toBeUndefined(); + }); + + it('excludes specified procedures with excludedProcedures', async () => { + const options = { + procedures: mockProcedures as any, + slicing: { + excludedProcedures: ['signUp', 'setAdmin', 'createMultiple'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(proceduresSchema, options); + + // Non-excluded procedures should be accessible + expect(db.$procs.getUser).toBeDefined(); + expect(db.$procs.listUsers).toBeDefined(); + expect(db.$procs.getOverview).toBeDefined(); + + // Excluded procedures should not be accessible + // @ts-expect-error - signUp should be excluded + expect(db.$procs.signUp).toBeUndefined(); + // @ts-expect-error - setAdmin should be excluded + expect(db.$procs.setAdmin).toBeUndefined(); + // @ts-expect-error - createMultiple should be excluded + expect(db.$procs.createMultiple).toBeUndefined(); + }); + + it('applies both includedProcedures and excludedProcedures (exclusion takes precedence)', async () => { + const options = { + procedures: mockProcedures as any, + slicing: { + includedProcedures: ['getUser', 'listUsers', 'signUp'] as const, + excludedProcedures: ['signUp'] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(proceduresSchema, options); + + // Only getUser and listUsers should be accessible + expect(db.$procs.getUser).toBeDefined(); + expect(db.$procs.listUsers).toBeDefined(); + + // signUp should be excluded despite being in includedProcedures + // @ts-expect-error - signUp should be excluded + expect(db.$procs.signUp).toBeUndefined(); + + // Others were never included + // @ts-expect-error - setAdmin was not included + expect(db.$procs.setAdmin).toBeUndefined(); + // @ts-expect-error - getOverview was not included + expect(db.$procs.getOverview).toBeUndefined(); + // @ts-expect-error - createMultiple was not included + expect(db.$procs.createMultiple).toBeUndefined(); + }); + + it('excludes all procedures when includedProcedures is empty array', async () => { + const options = { + procedures: mockProcedures as any, + slicing: { + includedProcedures: [] as const, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(proceduresSchema, options); + + // No procedures should be accessible with empty includedProcedures + // @ts-expect-error - getUser should not be accessible + expect(db.$procs.getUser).toBeUndefined(); + // @ts-expect-error - listUsers should not be accessible + expect(db.$procs.listUsers).toBeUndefined(); + // @ts-expect-error - signUp should not be accessible + expect(db.$procs.signUp).toBeUndefined(); + // @ts-expect-error - setAdmin should not be accessible + expect(db.$procs.setAdmin).toBeUndefined(); + // @ts-expect-error - getOverview should not be accessible + expect(db.$procs.getOverview).toBeUndefined(); + // @ts-expect-error - createMultiple should not be accessible + expect(db.$procs.createMultiple).toBeUndefined(); + }); + + it('has no effect when excludedProcedures is empty array', async () => { + const db = await createTestClient(proceduresSchema, { + procedures: mockProcedures as any, + slicing: { + excludedProcedures: [] as const, + }, + }); + + // All procedures should be accessible (empty excludedProcedures has no effect) + expect(db.$procs.getUser).toBeDefined(); + expect(db.$procs.listUsers).toBeDefined(); + expect(db.$procs.signUp).toBeDefined(); + expect(db.$procs.setAdmin).toBeDefined(); + expect(db.$procs.getOverview).toBeDefined(); + expect(db.$procs.createMultiple).toBeDefined(); + }); + }); + + describe('Filter kind inclusion/exclusion', () => { + describe('Model-level filter kind slicing', () => { + it('allows all filter kinds when no slicing config', async () => { + const db = await createTestClient(schema); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // All filter kinds should work + + // Equality filters + const equalityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(equalityResult).toHaveLength(1); + + // empty filters + const rangeResult = await db.user.findMany({ + where: {}, + }); + expect(rangeResult).toHaveLength(1); + + // Like filters + const likeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(likeResult).toHaveLength(1); + }); + + it('includes only specified filter kinds with includedFilterKinds', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work + const equalityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(equalityResult).toHaveLength(1); + + // Range filters should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - gte is not allowed (Range filters excluded) + where: { age: { gte: 20 } }, + }), + ).toBeRejectedByValidation(['"gte"']); + + // Like filters should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - contains is not allowed (Like filters excluded) + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + + it('excludes specified filter kinds with excludedFilterKinds', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + excludedFilterKinds: ['Like', 'Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work + const equalityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(equalityResult).toHaveLength(1); + + // Range filters should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - gte is excluded + where: { age: { gte: 20 } }, + }), + ).toBeRejectedByValidation(['"gte"']); + + // Like filters should cause type error + await expect( + db.user.findMany({ + // @ts-expect-error - contains is excluded + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + + it('applies both includedFilterKinds and excludedFilterKinds (exclusion takes precedence)', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality', 'Range', 'Like'] as const, + excludedFilterKinds: ['Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work + const equalityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(equalityResult).toHaveLength(1); + + // Like filters should work + const likeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(likeResult).toHaveLength(1); + + // Range filters should cause type error (excluded despite being included) + await expect( + db.user.findMany({ + // @ts-expect-error - gte is excluded + where: { age: { gte: 20 } }, + }), + ).toBeRejectedByValidation(['"gte"']); + }); + + it('excludes all filter operations when includedFilterKinds is empty', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: [] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // All filter operations should cause type errors + await expect( + db.user.findMany({ + // @ts-expect-error - no filter operators are allowed + where: { email: { equals: 'test@example.com' } }, + }), + ).toBeRejectedByValidation(['"where.email"']); + + await expect( + db.user.findMany({ + // @ts-expect-error - no filter operators are allowed + where: { age: { gte: 20 } }, + }), + ).toBeRejectedByValidation(['"where.age"']); + + await expect( + db.user.findMany({ + // @ts-expect-error - no filter operators are allowed + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"where.name"']); + }); + + it('allows only Equality and Range filters for numeric fields', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality', 'Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work + const inResult = await db.user.findMany({ + where: { age: { in: [25, 30] } }, + }); + expect(inResult).toHaveLength(1); + + // Range filters should work + const betweenResult = await db.user.findMany({ + where: { age: { between: [20, 30] } }, + }); + expect(betweenResult).toHaveLength(1); + + const gteResult = await db.user.findMany({ + where: { age: { gte: 25, lte: 30 } }, + }); + expect(gteResult).toHaveLength(1); + }); + + it('applies $all filter kind slicing to all models', async () => { + const options = { + slicing: { + models: { + $all: { + fields: { + $all: { + includedFilterKinds: ['Equality'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'user@example.com', name: 'User' } }); + const user = await db.user.findFirst({ where: { email: 'user@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Equality filters should work for all models + const userResult = await db.user.findMany({ + where: { email: { equals: 'user@example.com' } }, + }); + expect(userResult).toHaveLength(1); + + const postResult = await db.post.findMany({ + where: { title: { equals: 'Test Post' } }, + }); + expect(postResult).toHaveLength(1); + + // Like filters should cause type errors for all models + await expect( + db.user.findMany({ + // @ts-expect-error - contains is not allowed for User + where: { name: { contains: 'User' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + + await expect( + db.post.findMany({ + // @ts-expect-error - contains is not allowed for Post + where: { title: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + + it('model-specific filter kind slicing overrides $all slicing', async () => { + const options = { + slicing: { + models: { + $all: { + fields: { + $all: { + includedFilterKinds: ['Equality'] as const, + }, + }, + }, + user: { + fields: { + $all: { + includedFilterKinds: ['Equality', 'Like'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'user@example.com', name: 'Test User' } }); + const user = await db.user.findFirst({ where: { email: 'user@example.com' } }); + + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // User should have Equality and Like filters + const userLikeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(userLikeResult).toHaveLength(1); + + // Post should only have Equality filters (from $all) + const postEqualityResult = await db.post.findMany({ + where: { title: { equals: 'Test Post' } }, + }); + expect(postEqualityResult).toHaveLength(1); + + // Post should not have Like filters + await expect( + db.post.findMany({ + // @ts-expect-error - contains is not allowed for Post + where: { title: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + + it('excludes Relation filters to prevent relation queries', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + excludedFilterKinds: ['Relation'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'user@example.com', name: 'Test' } }); + + // Scalar filters should work + const scalarResult = await db.user.findMany({ + where: { email: { equals: 'user@example.com' } }, + }); + expect(scalarResult).toHaveLength(1); + + // Relation filters should cause type errors + await expect( + db.user.findMany({ + // @ts-expect-error - posts relation filter should be excluded + where: { posts: { some: { title: 'test' } } }, + }), + ).toBeRejectedByValidation(['"where.posts"']); + + await expect( + db.user.findMany({ + // @ts-expect-error - profile relation filter should be excluded + where: { profile: { is: { bio: 'test' } } }, + }), + ).toBeRejectedByValidation(['"where.profile"']); + }); + + it('uses $all excludedFilterKinds as fallback', async () => { + const options = { + slicing: { + models: { + $all: { + fields: { + $all: { + excludedFilterKinds: ['Relation', 'Json'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'user@example.com', name: 'User' } }); + const user = await db.user.findFirst({ where: { email: 'user@example.com' } }); + await db.post.create({ data: { title: 'Post', content: 'Content', authorId: user!.id } }); + + // Scalar filters should work for all models + const userResult = await db.user.findMany({ + where: { email: { equals: 'user@example.com' } }, + }); + expect(userResult).toHaveLength(1); + + // Relation filters should be excluded for all models + await expect( + db.user.findMany({ + // @ts-expect-error - posts relation filter excluded + where: { posts: { some: { title: 'test' } } }, + }), + ).toBeRejectedByValidation(['"where.posts"']); + + await expect( + db.post.findMany({ + // @ts-expect-error - author relation filter excluded + where: { author: { is: { email: 'test' } } }, + }), + ).toBeRejectedByValidation(['"where.author"']); + }); + }); + + describe('Field-level filter kind slicing', () => { + it('allows field-specific filter kind restrictions', async () => { + const options = { + slicing: { + models: { + user: { + // Field-level: restrict 'name' to only Equality filters + fields: { + name: { + includedFilterKinds: ['Equality'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work on 'name' field + const equalityResult = await db.user.findMany({ + where: { name: { equals: 'Test User' } }, + }); + expect(equalityResult).toHaveLength(1); + + // Like filters should cause type error on 'name' field + await expect( + db.user.findMany({ + // @ts-expect-error - contains is not allowed for 'name' field + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + + // Other fields should still support all filter kinds + const ageRangeResult = await db.user.findMany({ + where: { age: { gte: 20 } }, + }); + expect(ageRangeResult).toHaveLength(1); + + const emailLikeResult = await db.user.findMany({ + where: { email: { contains: 'example' } }, + }); + expect(emailLikeResult).toHaveLength(1); + }); + + it('excludes specific filter kinds for a field', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + email: { + excludedFilterKinds: ['Like'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work on 'email' field + const equalityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(equalityResult).toHaveLength(1); + + // Like filters should cause type error on 'email' field + await expect( + db.user.findMany({ + // @ts-expect-error - contains is excluded for 'email' field + where: { email: { contains: 'test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + + // Other fields should still support Like filters + const nameLikeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(nameLikeResult).toHaveLength(1); + }); + + it('applies both field-level includedFilterKinds and excludedFilterKinds', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + name: { + includedFilterKinds: ['Equality', 'Like', 'Range'] as const, + excludedFilterKinds: ['Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // Equality filters should work + const equalityResult = await db.user.findMany({ + where: { name: { equals: 'Test User' } }, + }); + expect(equalityResult).toHaveLength(1); + + // Like filters should work + const likeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(likeResult).toHaveLength(1); + + // Range filters should cause type error (excluded despite being included) + await expect( + db.user.findMany({ + // @ts-expect-error - Range filters are excluded for 'name' + where: { name: { gte: 'A' } }, + }), + ).toBeRejectedByValidation(['"gte"']); + }); + + it('field-level slicing with $all fallback', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + // $all: only Equality filters for all fields by default + $all: { + includedFilterKinds: ['Equality'] as const, + }, + // Field-level: 'name' gets Equality AND Like filters + name: { + includedFilterKinds: ['Equality', 'Like'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // 'name' field should have Like filters (field-level override) + const nameLikeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(nameLikeResult).toHaveLength(1); + + // 'email' field should only have Equality filters ($all fallback) + const emailEqualityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(emailEqualityResult).toHaveLength(1); + + // 'email' field should not have Like filters + await expect( + db.user.findMany({ + // @ts-expect-error - Like filters not allowed for 'email' (from $all) + where: { email: { contains: 'test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + + it('excludes all filter operations for a field when includedFilterKinds is empty', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + name: { + includedFilterKinds: [] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // All filter operations should cause type errors for 'name' field + await expect( + db.user.findMany({ + // @ts-expect-error - equals is not allowed for 'name' + where: { name: { equals: 'Test User' } }, + }), + ).toBeRejectedByValidation(['"where.name"']); + + await expect( + db.user.findMany({ + // @ts-expect-error - contains is not allowed for 'name' + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"where.name"']); + + // Other fields should still work normally + const emailResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(emailResult).toHaveLength(1); + }); + + it('allows different field-level slicing for multiple fields', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + name: { + includedFilterKinds: ['Equality'] as const, + }, + email: { + includedFilterKinds: ['Equality', 'Like'] as const, + }, + age: { + includedFilterKinds: ['Equality', 'Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // 'name' should only support Equality + const nameResult = await db.user.findMany({ + where: { name: { equals: 'Test User' } }, + }); + expect(nameResult).toHaveLength(1); + + await expect( + db.user.findMany({ + // @ts-expect-error - Like filters not allowed for 'name' + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + + // 'email' should support Equality and Like + const emailEqualityResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(emailEqualityResult).toHaveLength(1); + + const emailLikeResult = await db.user.findMany({ + where: { email: { contains: 'example' } }, + }); + expect(emailLikeResult).toHaveLength(1); + + await expect( + db.user.findMany({ + // @ts-expect-error - Range filters not allowed for 'email' + where: { email: { gte: 'a' } }, + }), + ).toBeRejectedByValidation(['"gte"']); + + // 'age' should support Equality and Range + const ageEqualityResult = await db.user.findMany({ + where: { age: { equals: 25 } }, + }); + expect(ageEqualityResult).toHaveLength(1); + + const ageRangeResult = await db.user.findMany({ + where: { age: { gte: 20, lte: 30 } }, + }); + expect(ageRangeResult).toHaveLength(1); + }); + + it('field-level excludedFilterKinds with $all fallback', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + // $all: exclude Range filters for all fields + $all: { + excludedFilterKinds: ['Range'] as const, + }, + // Field-level override + name: { + excludedFilterKinds: ['Like'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // 'name' should support Equality but not Like or Range + const nameEqualityResult = await db.user.findMany({ + where: { name: { equals: 'Test User' } }, + }); + expect(nameEqualityResult).toHaveLength(1); + + await expect( + db.user.findMany({ + // @ts-expect-error - Like filters excluded for 'name' field + where: { name: { contains: 'Test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + + await db.user.findMany({ + where: { name: { gte: 'A' } }, + }); + + // 'email' should support Equality and Like but not Range ($all excludes Range) + const emailLikeResult = await db.user.findMany({ + where: { email: { contains: 'example' } }, + }); + expect(emailLikeResult).toHaveLength(1); + + await expect( + db.user.findMany({ + // @ts-expect-error - Range filters excluded by $all + where: { email: { gte: 'a' } }, + }), + ).toBeRejectedByValidation(['"gte"']); + }); + + it('works with numeric fields', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + age: { + includedFilterKinds: ['Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test', age: 25 } }); + + // Range filters should work for 'age' + const gteResult = await db.user.findMany({ + where: { age: { gte: 20 } }, + }); + expect(gteResult).toHaveLength(1); + + const betweenResult = await db.user.findMany({ + where: { age: { between: [20, 30] } }, + }); + expect(betweenResult).toHaveLength(1); + + // Equality filters should cause type error for 'age' + await expect( + db.user.findMany({ + // @ts-expect-error - Equality filters not allowed for 'age' + where: { age: { equals: 25 } }, + }), + ).toBeRejectedByValidation(['"equals"']); + }); + + it('$all.fields specific-field config overrides $all.fields.$all', async () => { + // Verifies the precedence level: $all.fields[field] > $all.fields.$all + const options = { + slicing: { + models: { + $all: { + fields: { + // Default for every field on every model: Equality only + $all: { + includedFilterKinds: ['Equality'] as const, + }, + // Field-specific override within $all: 'name' gets Like too + name: { + includedFilterKinds: ['Equality', 'Like'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + // 'name' should allow Like ($all.fields.name wins over $all.fields.$all) + const nameLikeResult = await db.user.findMany({ + where: { name: { contains: 'Test' } }, + }); + expect(nameLikeResult).toHaveLength(1); + + // 'name' still allows Equality + const nameEqResult = await db.user.findMany({ + where: { name: { equals: 'Test User' } }, + }); + expect(nameEqResult).toHaveLength(1); + + // 'email' should only allow Equality (falls back to $all.fields.$all) + const emailEqResult = await db.user.findMany({ + where: { email: { equals: 'test@example.com' } }, + }); + expect(emailEqResult).toHaveLength(1); + + await expect( + db.user.findMany({ + // @ts-expect-error - Like not allowed for 'email' ($all.fields.$all) + where: { email: { contains: 'test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + + it('filter kind precedence: model[field] > model.$all > $all[field] > $all.$all', async () => { + // Exercises all four levels of the precedence chain end-to-end. + const options = { + slicing: { + models: { + $all: { + fields: { + // Level 4 (lowest): default for all fields on all models + $all: { + includedFilterKinds: ['Equality'] as const, + }, + // Level 3: 'title' field on any model gets Like too + title: { + includedFilterKinds: ['Equality', 'Like'] as const, + }, + }, + }, + user: { + fields: { + // Level 2: all User fields default to Equality + Range + $all: { + includedFilterKinds: ['Equality', 'Range'] as const, + }, + // Level 1 (highest): User.name also gets Like + name: { + includedFilterKinds: ['Equality', 'Like', 'Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + const user = await db.user.findFirst({ where: { email: 'test@example.com' } }); + await db.post.create({ data: { title: 'Test Post', content: 'Content', authorId: user!.id } }); + + // Level 1 – User.name: Equality + Like + Range + const nameLike = await db.user.findMany({ where: { name: { contains: 'Test' } } }); + expect(nameLike).toHaveLength(1); + const nameRange = await db.user.findMany({ where: { name: { gte: 'A' } } }); + expect(nameRange).toHaveLength(1); + + // Level 2 – User.email: Equality + Range (User.$all; Like is NOT included) + const emailRange = await db.user.findMany({ where: { age: { gte: 20 } } }); + expect(emailRange).toHaveLength(1); + await expect( + db.user.findMany({ + // @ts-expect-error - Like not allowed for User.email (User.$all wins over $all.fields.*) + where: { email: { contains: 'test' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + + // Level 3 – Post.title: Equality + Like ($all.fields.title; Range is NOT included) + const titleLike = await db.post.findMany({ where: { title: { contains: 'Test' } } }); + expect(titleLike).toHaveLength(1); + await expect( + db.post.findMany({ + // @ts-expect-error - Range not allowed for Post.title ($all.fields.title) + where: { title: { gte: 'A' } }, + }), + ).toBeRejectedByValidation(['"gte"']); + + // Level 4 – Post.content: Equality only ($all.fields.$all fallback) + const contentEq = await db.post.findMany({ where: { content: { equals: 'Content' } } }); + expect(contentEq).toHaveLength(1); + await expect( + db.post.findMany({ + // @ts-expect-error - Like not allowed for Post.content ($all.fields.$all) + where: { content: { contains: 'Content' } }, + }), + ).toBeRejectedByValidation(['"contains"']); + }); + }); + + describe('Direct value filter slicing', () => { + it('allows direct value filters when Equality kind is included', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Equality'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User' } }); + + const user = await db.user.findFirst({ + where: { email: 'test@example.com' }, + }); + expect(user?.email).toBe('test@example.com'); + }); + + it('rejects direct value filters when Equality kind is excluded', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'test@example.com', name: 'Test User', age: 25 } }); + + await expect( + db.user.findFirst({ + // @ts-expect-error - direct value shorthand maps to Equality filters + where: { email: 'test@example.com' }, + }), + ).toBeRejectedByValidation(['"where.email"']); + }); + + it('still allows unique operations to use direct value filters', async () => { + const options = { + slicing: { + models: { + user: { + fields: { + $all: { + includedFilterKinds: ['Range'] as const, + }, + }, + }, + }, + }, + dialect: {} as any, + } as const; + + const db = await createTestClient(schema, options); + + await db.user.create({ data: { email: 'unique@example.com', name: 'Original Name' } }); + + await expect( + db.user.findMany({ + // @ts-expect-error - findMany cannot use direct value filters without Equality kind + where: { email: 'unique@example.com' }, + }), + ).toBeRejectedByValidation(['"where.email"']); + + const uniqueUser = await db.user.findUnique({ + where: { email: 'unique@example.com' }, + }); + expect(uniqueUser?.name).toBe('Original Name'); + + await expect( + db.user.findUnique({ + // @ts-expect-error non-unique fields are still sliced + where: { email: 'unique@example.com', age: 10 }, + }), + ).toBeRejectedByValidation(['"where.age"']); + + const updated = await db.user.update({ + where: { email: 'unique@example.com' }, + data: { name: 'Updated Name' }, + }); + expect(updated.name).toBe('Updated Name'); + + const deleted = await db.user.delete({ + where: { email: 'unique@example.com' }, + }); + expect(deleted.email).toBe('unique@example.com'); + }); + }); + }); +}); diff --git a/tests/e2e/orm/schemas/basic/schema.ts b/tests/e2e/orm/schemas/basic/schema.ts index ea345b9ed..d42e0d89a 100644 --- a/tests/e2e/orm/schemas/basic/schema.ts +++ b/tests/e2e/orm/schemas/basic/schema.ts @@ -44,6 +44,11 @@ export class SchemaType implements SchemaDef { type: "String", optional: true }, + age: { + name: "age", + type: "Int", + optional: true + }, role: { name: "role", type: "Role", diff --git a/tests/e2e/orm/schemas/basic/schema.zmodel b/tests/e2e/orm/schemas/basic/schema.zmodel index 8fd48872c..9b2898bb1 100644 --- a/tests/e2e/orm/schemas/basic/schema.zmodel +++ b/tests/e2e/orm/schemas/basic/schema.zmodel @@ -21,6 +21,7 @@ type CommonFields { model User with CommonFields { email String @unique name String? + age Int? password String @ignore role Role @default(USER) posts Post[] From fd7c5ec858596faa367a94bb229465fcaa213366 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 19 Feb 2026 01:31:50 +0800 Subject: [PATCH 03/25] chore(orm): add aliased exports for all write operations (#2386) --- packages/orm/src/client/crud/operations/base.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 78dffd843..d696f82ff 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -170,6 +170,16 @@ export const AllReadOperations = [...CoreReadOperations, 'findUniqueOrThrow', 'f */ export type AllReadOperations = (typeof AllReadOperations)[number]; +/** + * List of all write operations - simply an alias of CoreWriteOperations. + */ +export const AllWriteOperations = CoreWriteOperations; + +/** + * List of all write operations - simply an alias of CoreWriteOperations. + */ +export type AllWriteOperations = CoreWriteOperations; + // context for nested relation operations export type FromRelationContext = { // the model where the relation field is defined From 3e03ab1506d90baee0de3c130748b1deefd2507f Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 19 Feb 2026 18:16:13 -0800 Subject: [PATCH 04/25] refactor: remove import from orm package in generated schema (#2387) --- packages/cli/src/actions/generate.ts | 23 ++- packages/cli/src/index.ts | 24 ++- packages/cli/src/plugins/typescript.ts | 8 + packages/cli/test/generate.test.ts | 144 ++++++++++++++++++ .../test/schemas/basic/schema-lite.ts | 2 +- .../test/schemas/procedures/schema-lite.ts | 2 +- packages/orm/src/client/options.ts | 16 +- packages/sdk/src/ts-schema-generator.ts | 59 ++++--- packages/testtools/src/project.ts | 2 +- pnpm-lock.yaml | 27 +++- samples/next.js/package.json | 1 + samples/next.js/zenstack/schema-lite.ts | 2 +- samples/next.js/zenstack/schema.ts | 2 +- samples/nuxt/package.json | 1 + samples/nuxt/zenstack/schema-lite.ts | 2 +- samples/nuxt/zenstack/schema.ts | 2 +- samples/orm/package.json | 3 +- samples/orm/zenstack/schema.ts | 4 +- samples/sveltekit/package.json | 1 + samples/sveltekit/src/zenstack/schema-lite.ts | 2 +- samples/sveltekit/src/zenstack/schema.ts | 2 +- tests/e2e/apps/rally/zenstack/schema.ts | 2 +- tests/e2e/github-repos/cal.com/schema.ts | 2 +- tests/e2e/github-repos/formbricks/schema.ts | 2 +- tests/e2e/github-repos/trigger.dev/schema.ts | 2 +- .../orm/plugin-infra/ext-query-args/schema.ts | 2 +- tests/e2e/orm/schemas/auth-type/schema.ts | 2 +- tests/e2e/orm/schemas/basic/schema.ts | 2 +- tests/e2e/orm/schemas/default-auth/schema.ts | 2 +- tests/e2e/orm/schemas/delegate/schema.ts | 2 +- tests/e2e/orm/schemas/json/schema.ts | 2 +- tests/e2e/orm/schemas/name-mapping/schema.ts | 2 +- tests/e2e/orm/schemas/omit/schema.ts | 2 +- tests/e2e/orm/schemas/petstore/schema.ts | 2 +- tests/e2e/orm/schemas/procedures/schema.ts | 2 +- tests/e2e/orm/schemas/todo/schema.ts | 2 +- tests/e2e/orm/schemas/typed-json/schema.ts | 2 +- tests/e2e/orm/schemas/typing/schema.ts | 4 +- tests/regression/package.json | 1 + tests/regression/test/issue-204/schema.ts | 2 +- tests/regression/test/issue-422/schema.ts | 2 +- tests/regression/test/issue-503/schema.ts | 2 +- tests/runtimes/bun/package.json | 1 + tests/runtimes/bun/schemas/schema.ts | 2 +- tests/runtimes/edge-runtime/package.json | 1 + tests/runtimes/edge-runtime/schemas/schema.ts | 2 +- turbo.json | 2 +- 47 files changed, 297 insertions(+), 83 deletions(-) diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index c014c02ef..351ecceb0 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -20,8 +20,10 @@ type Options = { output?: string; silent: boolean; watch: boolean; - lite: boolean; - liteOnly: boolean; + lite?: boolean; + liteOnly?: boolean; + generateModels?: boolean; + generateInput?: boolean; }; /** @@ -181,12 +183,18 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string, // merge CLI options if (provider === '@core/typescript') { - if (pluginOptions['lite'] === undefined) { + if (options.lite !== undefined) { pluginOptions['lite'] = options.lite; } - if (pluginOptions['liteOnly'] === undefined) { + if (options.liteOnly !== undefined) { pluginOptions['liteOnly'] = options.liteOnly; } + if (options.generateModels !== undefined) { + pluginOptions['generateModels'] = options.generateModels; + } + if (options.generateInput !== undefined) { + pluginOptions['generateInput'] = options.generateInput; + } } processedPlugins.push({ cliPlugin, pluginOptions }); @@ -196,7 +204,12 @@ async function runPlugins(schemaFile: string, model: Model, outputPath: string, const defaultPlugins = [ { plugin: corePlugins['typescript'], - options: { lite: options.lite, liteOnly: options.liteOnly }, + options: { + lite: options.lite, + liteOnly: options.liteOnly, + generateModels: options.generateModels, + generateInput: options.generateInput, + }, }, ]; defaultPlugins.forEach(({ plugin, options }) => { diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index bc52a9803..fcc4685c3 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -43,6 +43,14 @@ const proxyAction = async (options: Parameters[0]): Promis await telemetry.trackCommand('proxy', () => actions.proxy(options)); }; +function triStateBooleanOption(flag: string, description: string) { + return new Option(flag, description).choices(['true', 'false']).argParser((value) => { + if (value === undefined || value === 'true') return true; + if (value === 'false') return false; + throw new CliError(`Invalid value for ${flag}: ${value}`); + }); +} + function createProgram() { const program = new Command('zen') .alias('zenstack') @@ -74,8 +82,20 @@ function createProgram() { .addOption(noVersionCheckOption) .addOption(new Option('-o, --output ', 'default output directory for code generation')) .addOption(new Option('-w, --watch', 'enable watch mode').default(false)) - .addOption(new Option('--lite', 'also generate a lite version of schema without attributes').default(false)) - .addOption(new Option('--lite-only', 'only generate lite version of schema without attributes').default(false)) + .addOption( + triStateBooleanOption( + '--lite [boolean]', + 'also generate a lite version of schema without attributes, defaults to false', + ), + ) + .addOption( + triStateBooleanOption( + '--lite-only [boolean]', + 'only generate lite version of schema without attributes, defaults to false', + ), + ) + .addOption(triStateBooleanOption('--generate-models [boolean]', 'generate models.ts file, defaults to true')) + .addOption(triStateBooleanOption('--generate-input [boolean]', 'generate input.ts file, defaults to true')) .addOption(new Option('--silent', 'suppress all output except errors').default(false)) .action(generateAction); diff --git a/packages/cli/src/plugins/typescript.ts b/packages/cli/src/plugins/typescript.ts index 8b3465e80..80576ac35 100644 --- a/packages/cli/src/plugins/typescript.ts +++ b/packages/cli/src/plugins/typescript.ts @@ -28,11 +28,19 @@ const plugin: CliPlugin = { throw new Error('The "importWithFileExtension" option must be a string if specified.'); } + // whether to generate models.ts + const generateModelTypes = pluginOptions['generateModels'] !== false; + + // whether to generate input.ts + const generateInputTypes = pluginOptions['generateInput'] !== false; + await new TsSchemaGenerator().generate(model, { outDir, lite, liteOnly, importWithFileExtension: importWithFileExtension as string | undefined, + generateModelTypes, + generateInputTypes, }); }, }; diff --git a/packages/cli/test/generate.test.ts b/packages/cli/test/generate.test.ts index 6b270b4a8..646cbb680 100644 --- a/packages/cli/test/generate.test.ts +++ b/packages/cli/test/generate.test.ts @@ -60,6 +60,40 @@ describe('CLI generate command test', () => { expect(fs.existsSync(path.join(workDir, 'bar/schema.ts'))).toBe(true); }); + it('should respect plugin lite options', async () => { + const modelWithPlugin = ` +plugin typescript { + provider = "@core/typescript" + lite = true +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithPlugin); + runCli('generate', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true); + }); + + it('should respect plugin lite-only options', async () => { + const modelWithPlugin = ` +plugin typescript { + provider = "@core/typescript" + liteOnly = true +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithPlugin); + runCli('generate', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true); + }); + it('should respect lite option', async () => { const { workDir } = await createProject(model); runCli('generate --lite', workDir); @@ -73,4 +107,114 @@ describe('CLI generate command test', () => { expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false); expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true); }); + + it('should respect explicit liteOnly true option', async () => { + const { workDir } = await createProject(model); + runCli('generate --lite-only=true', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(false); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(true); + }); + + it('should respect explicit liteOnly false option', async () => { + const { workDir } = await createProject(model); + runCli('generate --lite-only=false', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(false); + }); + + it('should prefer CLI options over @core/typescript plugin settings for lite and liteOnly', async () => { + const modelWithPlugin = ` +plugin typescript { + provider = "@core/typescript" + lite = true + liteOnly = true +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithPlugin); + runCli('generate --lite=false --lite-only=false', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema-lite.ts'))).toBe(false); + }); + + it('should generate models.ts and input.ts by default', async () => { + const { workDir } = await createProject(model); + runCli('generate', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true); + }); + + it('should respect plugin options for generateModels and generateInput by default', async () => { + const modelWithPlugin = ` +plugin typescript { + provider = "@core/typescript" + generateModels = false + generateInput = false +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithPlugin); + runCli('generate', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(false); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(false); + }); + + it('should generate models.ts when --generate-models=true is passed', async () => { + const { workDir } = await createProject(model); + runCli('generate --generate-models=true', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true); + }); + + it('should not generate models.ts when --generate-models=false is passed', async () => { + const { workDir } = await createProject(model); + runCli('generate --generate-models=false', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(false); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true); + }); + + it('should generate input.ts when --generate-input=true is passed', async () => { + const { workDir } = await createProject(model); + runCli('generate --generate-input=true', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true); + }); + + it('should not generate input.ts when --generate-input=false is passed', async () => { + const { workDir } = await createProject(model); + runCli('generate --generate-input=false', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(false); + }); + + it('should prefer CLI options over @core/typescript plugin settings for generateModels and generateInput', async () => { + const modelWithPlugin = ` +plugin typescript { + provider = "@core/typescript" + generateModels = false + generateInput = false +} + +model User { + id String @id @default(cuid()) +} +`; + const { workDir } = await createProject(modelWithPlugin); + runCli('generate --generate-models --generate-input', workDir); + expect(fs.existsSync(path.join(workDir, 'zenstack/schema.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/models.ts'))).toBe(true); + expect(fs.existsSync(path.join(workDir, 'zenstack/input.ts'))).toBe(true); + }); }); diff --git a/packages/clients/tanstack-query/test/schemas/basic/schema-lite.ts b/packages/clients/tanstack-query/test/schemas/basic/schema-lite.ts index 918a37373..af9b66ee6 100644 --- a/packages/clients/tanstack-query/test/schemas/basic/schema-lite.ts +++ b/packages/clients/tanstack-query/test/schemas/basic/schema-lite.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts b/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts index 630d31410..8dc25a865 100644 --- a/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts +++ b/packages/clients/tanstack-query/test/schemas/procedures/schema-lite.ts @@ -2,7 +2,7 @@ // NOTE: Test fixture schema used for TanStack Query typing tests. // ////////////////////////////////////////////////////////////////////////////////////////////// -import { type SchemaDef, ExpressionUtils } from '@zenstackhq/orm/schema'; +import { type SchemaDef, ExpressionUtils } from '@zenstackhq/schema'; export class SchemaType implements SchemaDef { provider = { diff --git a/packages/orm/src/client/options.ts b/packages/orm/src/client/options.ts index 80aa82261..2061ebafa 100644 --- a/packages/orm/src/client/options.ts +++ b/packages/orm/src/client/options.ts @@ -1,6 +1,5 @@ -import type { Dialect, Expression, ExpressionBuilder, KyselyConfig } from 'kysely'; +import type { Dialect, Expression, ExpressionBuilder, KyselyConfig, OperandExpression } from 'kysely'; import type { GetModel, GetModelFields, GetModels, ProcedureDef, ScalarFields, SchemaDef } from '../schema'; -import type { PrependParameter } from '../utils/type-utils'; import type { FilterPropertyToKind } from './constants'; import type { ClientContract, CRUD_EXT } from './contract'; import type { GetProcedureNames, ProcedureHandlerFunc } from './crud-types'; @@ -226,10 +225,15 @@ export type OmitConfig = { export type ComputedFieldsOptions = { [Model in GetModels as 'computedFields' extends keyof GetModel ? Model : never]: { - [Field in keyof Schema['models'][Model]['computedFields']]: PrependParameter< - ExpressionBuilder, Model>, - Schema['models'][Model]['computedFields'][Field] - >; + [Field in keyof Schema['models'][Model]['computedFields']]: Schema['models'][Model]['computedFields'][Field] extends infer Func + ? Func extends (...args: any[]) => infer R + ? ( + // inject a first parameter for expression builder + p: ExpressionBuilder, Model>, + ...args: Parameters + ) => OperandExpression // wrap the return type with Kysely `OperandExpression` + : never + : never; }; }; diff --git a/packages/sdk/src/ts-schema-generator.ts b/packages/sdk/src/ts-schema-generator.ts index ac6fcf00f..90f6ceafa 100644 --- a/packages/sdk/src/ts-schema-generator.ts +++ b/packages/sdk/src/ts-schema-generator.ts @@ -57,6 +57,8 @@ export type TsSchemaGeneratorOptions = { lite?: boolean; liteOnly?: boolean; importWithFileExtension?: string; + generateModelTypes?: boolean; + generateInputTypes?: boolean; }; export class TsSchemaGenerator { @@ -72,10 +74,14 @@ export class TsSchemaGenerator { this.generateSchema(model, options); // the model types - this.generateModelsAndTypeDefs(model, options); + if (options.generateModelTypes !== false) { + this.generateModelsAndTypeDefs(model, options); + } // the input types - this.generateInputTypes(model, options); + if (options.generateInputTypes !== false) { + this.generateInputTypes(model, options); + } } private generateSchema(model: Model, options: TsSchemaGeneratorOptions) { @@ -111,31 +117,18 @@ export class TsSchemaGenerator { } private generateSchemaStatements(model: Model, statements: ts.Statement[], lite: boolean) { - const hasComputedFields = model.declarations.some( - (d) => isDataModel(d) && d.fields.some((f) => hasAttribute(f, '@computed')), - ); - // Generate schema content first to determine if ExpressionUtils is needed const schemaClass = this.createSchemaClass(model, lite); // Now generate the import declaration with the correct imports - // import { type SchemaDef, type OperandExpression, ExpressionUtils } from '@zenstackhq/orm/schema'; - const runtimeImportDecl = ts.factory.createImportDeclaration( + // import { type SchemaDef, ExpressionUtils } from '@zenstackhq/schema'; + const schemaImportDecl = ts.factory.createImportDeclaration( undefined, ts.factory.createImportClause( undefined, undefined, ts.factory.createNamedImports([ ts.factory.createImportSpecifier(true, undefined, ts.factory.createIdentifier('SchemaDef')), - ...(hasComputedFields - ? [ - ts.factory.createImportSpecifier( - true, - undefined, - ts.factory.createIdentifier('OperandExpression'), - ), - ] - : []), ...(this.usedExpressionUtils ? [ ts.factory.createImportSpecifier( @@ -147,9 +140,9 @@ export class TsSchemaGenerator { : []), ]), ), - ts.factory.createStringLiteral('@zenstackhq/orm/schema'), + ts.factory.createStringLiteral('@zenstackhq/schema'), ); - statements.push(runtimeImportDecl); + statements.push(schemaImportDecl); statements.push(schemaClass); @@ -503,9 +496,7 @@ export class TsSchemaGenerator { undefined, ), ], - ts.factory.createTypeReferenceNode('OperandExpression', [ - ts.factory.createTypeReferenceNode(this.mapFieldTypeToTSType(field.type)), - ]), + ts.factory.createTypeReferenceNode(this.mapFieldTypeToTSType(field.type)), ts.factory.createBlock( [ ts.factory.createThrowStatement( @@ -524,9 +515,14 @@ export class TsSchemaGenerator { private createUpdatedAtObject(ignoreArg: AttributeArg) { return ts.factory.createObjectLiteralExpression([ - ts.factory.createPropertyAssignment('ignore', ts.factory.createArrayLiteralExpression( - (ignoreArg.value as ArrayExpr).items.map((item) => ts.factory.createStringLiteral((item as ReferenceExpr).target.$refText)) - )) + ts.factory.createPropertyAssignment( + 'ignore', + ts.factory.createArrayLiteralExpression( + (ignoreArg.value as ArrayExpr).items.map((item) => + ts.factory.createStringLiteral((item as ReferenceExpr).target.$refText), + ), + ), + ), ]); } @@ -574,12 +570,13 @@ export class TsSchemaGenerator { const updatedAtAttrib = getAttribute(field, '@updatedAt') as DataFieldAttribute | undefined; if (updatedAtAttrib) { - const ignoreArg = updatedAtAttrib.args.find(arg => arg.$resolvedParam?.name === 'ignore'); - objectFields.push(ts.factory.createPropertyAssignment('updatedAt', - ignoreArg - ? this.createUpdatedAtObject(ignoreArg) - : ts.factory.createTrue() - )); + const ignoreArg = updatedAtAttrib.args.find((arg) => arg.$resolvedParam?.name === 'ignore'); + objectFields.push( + ts.factory.createPropertyAssignment( + 'updatedAt', + ignoreArg ? this.createUpdatedAtObject(ignoreArg) : ts.factory.createTrue(), + ), + ); } if (hasAttribute(field, '@omit')) { diff --git a/packages/testtools/src/project.ts b/packages/testtools/src/project.ts index 9ea3478bf..20200e676 100644 --- a/packages/testtools/src/project.ts +++ b/packages/testtools/src/project.ts @@ -21,7 +21,7 @@ export function createTestProject(zmodelContent?: string) { } // in addition, symlink zenstack packages - const zenstackPackages = ['language', 'sdk', 'orm', 'cli']; + const zenstackPackages = ['language', 'sdk', 'schema', 'orm', 'cli']; fs.mkdirSync(path.join(workDir, 'node_modules/@zenstackhq')); for (const pkg of zenstackPackages) { fs.symlinkSync( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 45ac2f741..5828dbe57 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -853,6 +853,9 @@ importers: '@zenstackhq/orm': specifier: workspace:* version: link:../../packages/orm + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../packages/schema '@zenstackhq/server': specifier: workspace:* version: link:../../packages/server @@ -920,6 +923,9 @@ importers: '@zenstackhq/orm': specifier: workspace:* version: link:../../packages/orm + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../packages/schema '@zenstackhq/server': specifier: workspace:* version: link:../../packages/server @@ -960,6 +966,9 @@ importers: '@zenstackhq/plugin-policy': specifier: workspace:* version: link:../../packages/plugins/policy + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../packages/schema better-sqlite3: specifier: 'catalog:' version: 12.5.0 @@ -991,6 +1000,9 @@ importers: '@zenstackhq/orm': specifier: workspace:* version: link:../../packages/orm + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../packages/schema '@zenstackhq/server': specifier: workspace:* version: link:../../packages/server @@ -1132,6 +1144,9 @@ importers: '@zenstackhq/plugin-policy': specifier: workspace:* version: link:../../packages/plugins/policy + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../packages/schema '@zenstackhq/sdk': specifier: workspace:* version: link:../../packages/sdk @@ -1156,6 +1171,9 @@ importers: '@zenstackhq/plugin-policy': specifier: workspace:* version: link:../../../packages/plugins/policy + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../../packages/schema '@zenstackhq/testtools': specifier: workspace:* version: link:../../../packages/testtools @@ -1196,6 +1214,9 @@ importers: '@zenstackhq/plugin-policy': specifier: workspace:* version: link:../../../packages/plugins/policy + '@zenstackhq/schema': + specifier: workspace:* + version: link:../../../packages/schema '@zenstackhq/testtools': specifier: workspace:* version: link:../../../packages/testtools @@ -13043,7 +13064,7 @@ snapshots: eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.6.1)) @@ -13076,7 +13097,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -13091,7 +13112,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 diff --git a/samples/next.js/package.json b/samples/next.js/package.json index 88839b7c0..1a58b53c2 100644 --- a/samples/next.js/package.json +++ b/samples/next.js/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@tanstack/react-query": "catalog:", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/server": "workspace:*", "@zenstackhq/tanstack-query": "workspace:*", diff --git a/samples/next.js/zenstack/schema-lite.ts b/samples/next.js/zenstack/schema-lite.ts index 7308e184d..0f5ad88bc 100644 --- a/samples/next.js/zenstack/schema-lite.ts +++ b/samples/next.js/zenstack/schema-lite.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/samples/next.js/zenstack/schema.ts b/samples/next.js/zenstack/schema.ts index 724c9659f..c420179f7 100644 --- a/samples/next.js/zenstack/schema.ts +++ b/samples/next.js/zenstack/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/samples/nuxt/package.json b/samples/nuxt/package.json index 6c0ec8c43..4145dc0aa 100644 --- a/samples/nuxt/package.json +++ b/samples/nuxt/package.json @@ -13,6 +13,7 @@ "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/vue-query": "catalog:", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/server": "workspace:*", "@zenstackhq/tanstack-query": "workspace:*", diff --git a/samples/nuxt/zenstack/schema-lite.ts b/samples/nuxt/zenstack/schema-lite.ts index 7308e184d..0f5ad88bc 100644 --- a/samples/nuxt/zenstack/schema-lite.ts +++ b/samples/nuxt/zenstack/schema-lite.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/samples/nuxt/zenstack/schema.ts b/samples/nuxt/zenstack/schema.ts index 724c9659f..c420179f7 100644 --- a/samples/nuxt/zenstack/schema.ts +++ b/samples/nuxt/zenstack/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/samples/orm/package.json b/samples/orm/package.json index 18591115b..712b30535 100644 --- a/samples/orm/package.json +++ b/samples/orm/package.json @@ -1,5 +1,5 @@ { - "name": "sample-blog", + "name": "sample-orm", "version": "3.3.3", "description": "", "main": "index.js", @@ -15,6 +15,7 @@ "author": "", "license": "MIT", "dependencies": { + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/plugin-policy": "workspace:*", "better-sqlite3": "catalog:", diff --git a/samples/orm/zenstack/schema.ts b/samples/orm/zenstack/schema.ts index 25ef6342a..aa0d92bc6 100644 --- a/samples/orm/zenstack/schema.ts +++ b/samples/orm/zenstack/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, type OperandExpression, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" @@ -81,7 +81,7 @@ export class SchemaType implements SchemaDef { computedFields: { postCount(_context: { modelAlias: string; - }): OperandExpression { + }): number { throw new Error("This is a stub for computed field"); } } diff --git a/samples/sveltekit/package.json b/samples/sveltekit/package.json index 503b59b4e..0d1c56779 100644 --- a/samples/sveltekit/package.json +++ b/samples/sveltekit/package.json @@ -14,6 +14,7 @@ }, "dependencies": { "@tanstack/svelte-query": "catalog:", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/server": "workspace:*", "@zenstackhq/tanstack-query": "workspace:*", diff --git a/samples/sveltekit/src/zenstack/schema-lite.ts b/samples/sveltekit/src/zenstack/schema-lite.ts index 7308e184d..0f5ad88bc 100644 --- a/samples/sveltekit/src/zenstack/schema-lite.ts +++ b/samples/sveltekit/src/zenstack/schema-lite.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/samples/sveltekit/src/zenstack/schema.ts b/samples/sveltekit/src/zenstack/schema.ts index 724c9659f..c420179f7 100644 --- a/samples/sveltekit/src/zenstack/schema.ts +++ b/samples/sveltekit/src/zenstack/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/apps/rally/zenstack/schema.ts b/tests/e2e/apps/rally/zenstack/schema.ts index dd860a2b0..a729240c7 100644 --- a/tests/e2e/apps/rally/zenstack/schema.ts +++ b/tests/e2e/apps/rally/zenstack/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "postgresql" diff --git a/tests/e2e/github-repos/cal.com/schema.ts b/tests/e2e/github-repos/cal.com/schema.ts index e77063c76..dc3e4194b 100644 --- a/tests/e2e/github-repos/cal.com/schema.ts +++ b/tests/e2e/github-repos/cal.com/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "postgresql" diff --git a/tests/e2e/github-repos/formbricks/schema.ts b/tests/e2e/github-repos/formbricks/schema.ts index fe29237a3..825df4e9d 100644 --- a/tests/e2e/github-repos/formbricks/schema.ts +++ b/tests/e2e/github-repos/formbricks/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "postgresql" diff --git a/tests/e2e/github-repos/trigger.dev/schema.ts b/tests/e2e/github-repos/trigger.dev/schema.ts index 29c733500..4e4156b9f 100644 --- a/tests/e2e/github-repos/trigger.dev/schema.ts +++ b/tests/e2e/github-repos/trigger.dev/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "postgresql" diff --git a/tests/e2e/orm/plugin-infra/ext-query-args/schema.ts b/tests/e2e/orm/plugin-infra/ext-query-args/schema.ts index a8f0ffb86..57d0d946f 100644 --- a/tests/e2e/orm/plugin-infra/ext-query-args/schema.ts +++ b/tests/e2e/orm/plugin-infra/ext-query-args/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/auth-type/schema.ts b/tests/e2e/orm/schemas/auth-type/schema.ts index c57c43903..b4e3b472e 100644 --- a/tests/e2e/orm/schemas/auth-type/schema.ts +++ b/tests/e2e/orm/schemas/auth-type/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef } from "@zenstackhq/orm/schema"; +import { type SchemaDef } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/basic/schema.ts b/tests/e2e/orm/schemas/basic/schema.ts index d42e0d89a..15591490c 100644 --- a/tests/e2e/orm/schemas/basic/schema.ts +++ b/tests/e2e/orm/schemas/basic/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/default-auth/schema.ts b/tests/e2e/orm/schemas/default-auth/schema.ts index cdc982776..ff265850a 100644 --- a/tests/e2e/orm/schemas/default-auth/schema.ts +++ b/tests/e2e/orm/schemas/default-auth/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/delegate/schema.ts b/tests/e2e/orm/schemas/delegate/schema.ts index b3fabd6ea..d5488db19 100644 --- a/tests/e2e/orm/schemas/delegate/schema.ts +++ b/tests/e2e/orm/schemas/delegate/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/json/schema.ts b/tests/e2e/orm/schemas/json/schema.ts index b5537a9ff..6e3e9ebd2 100644 --- a/tests/e2e/orm/schemas/json/schema.ts +++ b/tests/e2e/orm/schemas/json/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/name-mapping/schema.ts b/tests/e2e/orm/schemas/name-mapping/schema.ts index e39aeb951..1479f200c 100644 --- a/tests/e2e/orm/schemas/name-mapping/schema.ts +++ b/tests/e2e/orm/schemas/name-mapping/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/omit/schema.ts b/tests/e2e/orm/schemas/omit/schema.ts index 29391a36e..16ebb237c 100644 --- a/tests/e2e/orm/schemas/omit/schema.ts +++ b/tests/e2e/orm/schemas/omit/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/petstore/schema.ts b/tests/e2e/orm/schemas/petstore/schema.ts index feebb2972..93831438c 100644 --- a/tests/e2e/orm/schemas/petstore/schema.ts +++ b/tests/e2e/orm/schemas/petstore/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/procedures/schema.ts b/tests/e2e/orm/schemas/procedures/schema.ts index bd2b10018..3d469965c 100644 --- a/tests/e2e/orm/schemas/procedures/schema.ts +++ b/tests/e2e/orm/schemas/procedures/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/todo/schema.ts b/tests/e2e/orm/schemas/todo/schema.ts index 2df7b731b..d64b7d78b 100644 --- a/tests/e2e/orm/schemas/todo/schema.ts +++ b/tests/e2e/orm/schemas/todo/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/typed-json/schema.ts b/tests/e2e/orm/schemas/typed-json/schema.ts index d99f97b5d..7c9f6a1d1 100644 --- a/tests/e2e/orm/schemas/typed-json/schema.ts +++ b/tests/e2e/orm/schemas/typed-json/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/e2e/orm/schemas/typing/schema.ts b/tests/e2e/orm/schemas/typing/schema.ts index 58325be74..42789446c 100644 --- a/tests/e2e/orm/schemas/typing/schema.ts +++ b/tests/e2e/orm/schemas/typing/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, type OperandExpression, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "postgresql" @@ -87,7 +87,7 @@ export class SchemaType implements SchemaDef { computedFields: { postCount(_context: { modelAlias: string; - }): OperandExpression { + }): number { throw new Error("This is a stub for computed field"); } } diff --git a/tests/regression/package.json b/tests/regression/package.json index 5efca0c45..cbbdc2656 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -18,6 +18,7 @@ "devDependencies": { "@zenstackhq/cli": "workspace:*", "@zenstackhq/language": "workspace:*", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/sdk": "workspace:*", "@zenstackhq/plugin-policy": "workspace:*", diff --git a/tests/regression/test/issue-204/schema.ts b/tests/regression/test/issue-204/schema.ts index 3c8726b39..aeb22070f 100644 --- a/tests/regression/test/issue-204/schema.ts +++ b/tests/regression/test/issue-204/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef } from "@zenstackhq/orm/schema"; +import { type SchemaDef } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/regression/test/issue-422/schema.ts b/tests/regression/test/issue-422/schema.ts index 32450381f..04cf0c85c 100644 --- a/tests/regression/test/issue-422/schema.ts +++ b/tests/regression/test/issue-422/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/regression/test/issue-503/schema.ts b/tests/regression/test/issue-503/schema.ts index 702a67e37..6bbfeac03 100644 --- a/tests/regression/test/issue-503/schema.ts +++ b/tests/regression/test/issue-503/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/runtimes/bun/package.json b/tests/runtimes/bun/package.json index df0257e05..eff1b55e2 100644 --- a/tests/runtimes/bun/package.json +++ b/tests/runtimes/bun/package.json @@ -11,6 +11,7 @@ }, "dependencies": { "@zenstackhq/cli": "workspace:*", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/plugin-policy": "workspace:*", "@zenstackhq/common-helpers": "workspace:*", diff --git a/tests/runtimes/bun/schemas/schema.ts b/tests/runtimes/bun/schemas/schema.ts index 5dfb25346..8207633d9 100644 --- a/tests/runtimes/bun/schemas/schema.ts +++ b/tests/runtimes/bun/schemas/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "sqlite" diff --git a/tests/runtimes/edge-runtime/package.json b/tests/runtimes/edge-runtime/package.json index dc3eebf7a..07b7b068a 100644 --- a/tests/runtimes/edge-runtime/package.json +++ b/tests/runtimes/edge-runtime/package.json @@ -13,6 +13,7 @@ "@edge-runtime/vm": "^5.0.0", "@zenstackhq/cli": "workspace:*", "@zenstackhq/common-helpers": "workspace:*", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", "@zenstackhq/plugin-policy": "workspace:*", "@zenstackhq/testtools": "workspace:*", diff --git a/tests/runtimes/edge-runtime/schemas/schema.ts b/tests/runtimes/edge-runtime/schemas/schema.ts index 367e5f123..cf2521709 100644 --- a/tests/runtimes/edge-runtime/schemas/schema.ts +++ b/tests/runtimes/edge-runtime/schemas/schema.ts @@ -5,7 +5,7 @@ /* eslint-disable */ -import { type SchemaDef, ExpressionUtils } from "@zenstackhq/orm/schema"; +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; export class SchemaType implements SchemaDef { provider = { type: "postgresql" diff --git a/turbo.json b/turbo.json index 6e619eeb7..a77b9a4d4 100644 --- a/turbo.json +++ b/turbo.json @@ -4,7 +4,7 @@ "build": { "dependsOn": ["^build"], "inputs": ["src/**", "samples/**", "zenstack/*.zmodel"], - "outputs": ["dist/**"] + "outputs": ["dist/**", ".output/**"] }, "watch": { "dependsOn": ["^build"], From 7a98d4106d7eb5f7745347a2d7337e37cb4b014a Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Fri, 20 Feb 2026 06:31:00 -0800 Subject: [PATCH 05/25] feat: creating zod schemas for zmodel constructs and ORM query input validations (#2389) --- packages/orm/package.json | 1 + packages/orm/src/client/client-impl.ts | 4 + packages/orm/src/client/constants.ts | 8 +- packages/orm/src/client/contract.ts | 131 +- packages/orm/src/client/crud-types.ts | 95 +- .../orm/src/client/crud/operations/find.ts | 6 +- .../orm/src/client/crud/validator/index.ts | 2255 +---------------- .../src/client/crud/validator/validator.ts | 288 +++ packages/orm/src/client/index.ts | 1 + .../validator => zod}/cache-decorator.ts | 0 packages/orm/src/client/zod/factory.ts | 2106 +++++++++++++++ packages/orm/src/client/zod/index.ts | 1 + packages/schema/package.json | 7 +- packages/schema/src/accessor.ts | 232 ++ packages/schema/src/index.ts | 1 + packages/schema/src/schema.ts | 18 +- packages/schema/test/accessor.test.ts | 117 + packages/schema/test/schema/schema.ts | 135 + packages/schema/test/schema/schema.zmodel | 32 + packages/schema/vitest.config.ts | 4 + packages/zod/package.json | 17 +- packages/zod/src/error.ts | 8 + packages/zod/src/factory.ts | 243 ++ packages/zod/src/index.ts | 35 +- packages/zod/src/types.ts | 27 - .../crud/validator => zod/src}/utils.ts | 29 +- packages/zod/test/factory.test.ts | 581 +++++ packages/zod/test/schema/schema.ts | 186 ++ packages/zod/test/schema/schema.zmodel | 46 + packages/zod/vitest.config.ts | 4 + pnpm-lock.yaml | 22 +- scripts/test-generate.ts | 9 +- tests/e2e/orm/client-api/create-many.test.ts | 5 - tests/e2e/orm/client-api/zod.test-d.ts | 366 +++ tests/e2e/orm/client-api/zod.test.ts | 1063 ++++++++ tests/e2e/vitest.config.ts | 3 + 36 files changed, 5626 insertions(+), 2460 deletions(-) create mode 100644 packages/orm/src/client/crud/validator/validator.ts rename packages/orm/src/client/{crud/validator => zod}/cache-decorator.ts (100%) create mode 100644 packages/orm/src/client/zod/factory.ts create mode 100644 packages/orm/src/client/zod/index.ts create mode 100644 packages/schema/src/accessor.ts create mode 100644 packages/schema/test/accessor.test.ts create mode 100644 packages/schema/test/schema/schema.ts create mode 100644 packages/schema/test/schema/schema.zmodel create mode 100644 packages/schema/vitest.config.ts create mode 100644 packages/zod/src/error.ts create mode 100644 packages/zod/src/factory.ts delete mode 100644 packages/zod/src/types.ts rename packages/{orm/src/client/crud/validator => zod/src}/utils.ts (96%) create mode 100644 packages/zod/test/factory.test.ts create mode 100644 packages/zod/test/schema/schema.ts create mode 100644 packages/zod/test/schema/schema.zmodel create mode 100644 packages/zod/vitest.config.ts create mode 100644 tests/e2e/orm/client-api/zod.test-d.ts create mode 100644 tests/e2e/orm/client-api/zod.test.ts diff --git a/packages/orm/package.json b/packages/orm/package.json index 170062927..ad6f1c57e 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -95,6 +95,7 @@ "@paralleldrive/cuid2": "^2.2.2", "@zenstackhq/common-helpers": "workspace:*", "@zenstackhq/schema": "workspace:*", + "@zenstackhq/zod": "workspace:*", "cuid": "^3.0.0", "decimal.js": "catalog:", "json-stable-stringify": "^1.3.0", diff --git a/packages/orm/src/client/client-impl.ts b/packages/orm/src/client/client-impl.ts index 8eec17a13..1aa289aa4 100644 --- a/packages/orm/src/client/client-impl.ts +++ b/packages/orm/src/client/client-impl.ts @@ -133,6 +133,10 @@ export class ClientImpl { return this.kyselyRaw; } + get $zod() { + return this.inputValidator.zodFactory; + } + get isTransaction() { return this.kysely.isTransaction; } diff --git a/packages/orm/src/client/constants.ts b/packages/orm/src/client/constants.ts index bf62faff6..a945b7da2 100644 --- a/packages/orm/src/client/constants.ts +++ b/packages/orm/src/client/constants.ts @@ -1,7 +1,7 @@ -/** - * The comment prefix for annotation generated Kysely queries with context information. - */ -export const CONTEXT_COMMENT_PREFIX = '-- $$context:'; +// /** +// * The comment prefix for annotation generated Kysely queries with context information. +// */ +// export const CONTEXT_COMMENT_PREFIX = '-- $$context:'; /** * The types of fields that are numeric. diff --git a/packages/orm/src/client/contract.ts b/packages/orm/src/client/contract.ts index 9b038722f..c6b772aa4 100644 --- a/packages/orm/src/client/contract.ts +++ b/packages/orm/src/client/contract.ts @@ -39,45 +39,15 @@ import type { UpdateManyArgs, UpsertArgs, } from './crud-types'; -import type { - CoreCreateOperations, - CoreCrudOperations, - CoreDeleteOperations, - CoreReadOperations, - CoreUpdateOperations, -} from './crud/operations/base'; import type { ClientOptions, QueryOptions } from './options'; import type { ExtClientMembersBase, ExtQueryArgsBase, RuntimePlugin } from './plugin'; import type { ZenStackPromise } from './promise'; import type { ToKysely } from './query-builder'; import type { GetSlicedModels, GetSlicedOperations, GetSlicedProcedures } from './type-utils'; +import type { ZodSchemaFactory } from './zod/factory'; type TransactionUnsupportedMethods = (typeof TRANSACTION_UNSUPPORTED_METHODS)[number]; -/** - * Extracts extended query args for a specific operation. - */ -type ExtractExtQueryArgs = (Operation extends keyof ExtQueryArgs - ? ExtQueryArgs[Operation] - : {}) & - ('$create' extends keyof ExtQueryArgs - ? Operation extends CoreCreateOperations - ? ExtQueryArgs['$create'] - : {} - : {}) & - ('$read' extends keyof ExtQueryArgs ? (Operation extends CoreReadOperations ? ExtQueryArgs['$read'] : {}) : {}) & - ('$update' extends keyof ExtQueryArgs - ? Operation extends CoreUpdateOperations - ? ExtQueryArgs['$update'] - : {} - : {}) & - ('$delete' extends keyof ExtQueryArgs - ? Operation extends CoreDeleteOperations - ? ExtQueryArgs['$delete'] - : {} - : {}) & - ('$all' extends keyof ExtQueryArgs ? ExtQueryArgs['$all'] : {}); - /** * Transaction isolation levels. */ @@ -232,6 +202,11 @@ export type ClientContract< */ $disconnect(): Promise; + /** + * Factory for creating zod schemas to validate query args. + */ + get $zod(): ZodSchemaFactory; + /** * Pushes the schema to the database. For testing purposes only. * @private @@ -317,7 +292,7 @@ export type AllModelOperations< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions, - ExtQueryArgs, + ExtQueryArgs extends ExtQueryArgsBase, > = CommonModelOperations & // provider-specific operations (Schema['provider']['type'] extends 'mysql' @@ -341,15 +316,8 @@ export type AllModelOperations< * }); * ``` */ - createManyAndReturn< - T extends CreateManyAndReturnArgs & - ExtractExtQueryArgs, - >( - args?: SelectSubset< - T, - CreateManyAndReturnArgs & - ExtractExtQueryArgs - >, + createManyAndReturn>( + args?: SelectSubset>, ): ZenStackPromise[]>; /** @@ -374,15 +342,8 @@ export type AllModelOperations< * }); * ``` */ - updateManyAndReturn< - T extends UpdateManyAndReturnArgs & - ExtractExtQueryArgs, - >( - args: Subset< - T, - UpdateManyAndReturnArgs & - ExtractExtQueryArgs - >, + updateManyAndReturn>( + args: Subset>, ): ZenStackPromise[]>; }); @@ -390,7 +351,7 @@ type CommonModelOperations< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions, - ExtQueryArgs, + ExtQueryArgs extends ExtQueryArgsBase, > = { /** * Returns a list of entities. @@ -473,8 +434,8 @@ type CommonModelOperations< * }); // result: `{ _count: { posts: number } }` * ``` */ - findMany & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + findMany>( + args?: SelectSubset>, ): ZenStackPromise[]>; /** @@ -483,8 +444,8 @@ type CommonModelOperations< * @returns a single entity or null if not found * @see {@link findMany} */ - findUnique & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + findUnique>( + args: SelectSubset>, ): ZenStackPromise | null>; /** @@ -493,10 +454,8 @@ type CommonModelOperations< * @returns a single entity * @see {@link findMany} */ - findUniqueOrThrow< - T extends FindUniqueArgs & ExtractExtQueryArgs, - >( - args: SelectSubset & ExtractExtQueryArgs>, + findUniqueOrThrow>( + args: SelectSubset>, ): ZenStackPromise>; /** @@ -505,8 +464,8 @@ type CommonModelOperations< * @returns a single entity or null if not found * @see {@link findMany} */ - findFirst & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + findFirst>( + args?: SelectSubset>, ): ZenStackPromise | null>; /** @@ -515,8 +474,8 @@ type CommonModelOperations< * @returns a single entity * @see {@link findMany} */ - findFirstOrThrow & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + findFirstOrThrow>( + args?: SelectSubset>, ): ZenStackPromise>; /** @@ -571,8 +530,8 @@ type CommonModelOperations< * }); * ``` */ - create & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + create>( + args: SelectSubset>, ): ZenStackPromise>; /** @@ -600,8 +559,8 @@ type CommonModelOperations< * }); * ``` */ - createMany & ExtractExtQueryArgs>( - args?: SelectSubset & ExtractExtQueryArgs>, + createMany>( + args?: SelectSubset>, ): ZenStackPromise; /** @@ -721,8 +680,8 @@ type CommonModelOperations< * }); * ``` */ - update & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + update>( + args: SelectSubset>, ): ZenStackPromise>; /** @@ -745,8 +704,8 @@ type CommonModelOperations< * limit: 10 * }); */ - updateMany & ExtractExtQueryArgs>( - args: Subset & ExtractExtQueryArgs>, + updateMany>( + args: Subset>, ): ZenStackPromise; /** @@ -769,8 +728,8 @@ type CommonModelOperations< * }); * ``` */ - upsert & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + upsert>( + args: SelectSubset>, ): ZenStackPromise>; /** @@ -792,8 +751,8 @@ type CommonModelOperations< * }); // result: `{ id: string; email: string }` * ``` */ - delete & ExtractExtQueryArgs>( - args: SelectSubset & ExtractExtQueryArgs>, + delete>( + args: SelectSubset>, ): ZenStackPromise>; /** @@ -815,8 +774,8 @@ type CommonModelOperations< * }); * ``` */ - deleteMany & ExtractExtQueryArgs>( - args?: Subset & ExtractExtQueryArgs>, + deleteMany>( + args?: Subset>, ): ZenStackPromise; /** @@ -837,8 +796,8 @@ type CommonModelOperations< * select: { _all: true, email: true } * }); // result: `{ _all: number, email: number }` */ - count & ExtractExtQueryArgs>( - args?: Subset & ExtractExtQueryArgs>, + count>( + args?: Subset>, ): ZenStackPromise>>; /** @@ -858,8 +817,8 @@ type CommonModelOperations< * _max: { age: true } * }); // result: `{ _count: number, _avg: { age: number }, ... }` */ - aggregate & ExtractExtQueryArgs>( - args: Subset & ExtractExtQueryArgs>, + aggregate>( + args: Subset>, ): ZenStackPromise>>; /** @@ -895,8 +854,8 @@ type CommonModelOperations< * having: { country: 'US', age: { _avg: { gte: 18 } } } * }); */ - groupBy & ExtractExtQueryArgs>( - args: Subset & ExtractExtQueryArgs>, + groupBy>( + args: Subset>, ): ZenStackPromise>>; /** @@ -916,8 +875,8 @@ type CommonModelOperations< * where: { posts: { some: { published: true } } }, * }); // result: `boolean` */ - exists & ExtractExtQueryArgs>( - args?: Subset & ExtractExtQueryArgs>, + exists>( + args?: Subset>, ): ZenStackPromise; }; @@ -927,7 +886,7 @@ export type ModelOperations< Schema extends SchemaDef, Model extends GetModels, Options extends ClientOptions = ClientOptions, - ExtQueryArgs = {}, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = SliceOperations, Schema, Model, Options>; //#endregion diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 5b050f56a..329c55b8e 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -50,7 +50,15 @@ import type { XOR, } from '../utils/type-utils'; import type { ClientContract } from './contract'; +import type { + CoreCreateOperations, + CoreCrudOperations, + CoreDeleteOperations, + CoreReadOperations, + CoreUpdateOperations, +} from './crud/operations/base'; import type { FilterKind, QueryOptions } from './options'; +import type { ExtQueryArgsBase } from './plugin'; import type { ToKyselySchema } from './query-builder'; import type { GetSlicedFilterKindsForField, GetSlicedModels } from './type-utils'; @@ -1213,27 +1221,32 @@ export type FindManyArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = FindArgs; + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = FindArgs & ExtractExtQueryArgs; export type FindFirstArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = FindArgs; + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = FindArgs & ExtractExtQueryArgs; export type ExistsArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = FilterArgs; + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = FilterArgs & ExtractExtQueryArgs; export type FindUniqueArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { where: WhereUniqueInput; -} & SelectIncludeOmit; +} & SelectIncludeOmit & + ExtractExtQueryArgs; //#endregion @@ -1243,17 +1256,27 @@ export type CreateArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { data: CreateInput; -} & SelectIncludeOmit; +} & SelectIncludeOmit & + ExtractExtQueryArgs; -export type CreateManyArgs> = CreateManyInput; +export type CreateManyArgs< + Schema extends SchemaDef, + Model extends GetModels, + _Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = CreateManyInput & ExtractExtQueryArgs; export type CreateManyAndReturnArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = CreateManyInput & SelectIncludeOmit; + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = CreateManyInput & + SelectIncludeOmit & + ExtractExtQueryArgs; type OptionalWrap, T extends object> = Optional< T, @@ -1460,6 +1483,7 @@ export type UpdateArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { /** * The data to update the record with. @@ -1470,19 +1494,24 @@ export type UpdateArgs< * The unique filter to find the record to update. */ where: WhereUniqueInput; -} & SelectIncludeOmit; +} & SelectIncludeOmit & + ExtractExtQueryArgs; export type UpdateManyArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = UpdateManyPayload; + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = UpdateManyPayload & ExtractExtQueryArgs; export type UpdateManyAndReturnArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, -> = UpdateManyPayload & SelectIncludeOmit; + ExtQueryArgs extends ExtQueryArgsBase = {}, +> = UpdateManyPayload & + SelectIncludeOmit & + ExtractExtQueryArgs; type UpdateManyPayload< Schema extends SchemaDef, @@ -1510,6 +1539,7 @@ export type UpsertArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { /** * The data to create the record if it doesn't exist. @@ -1525,7 +1555,8 @@ export type UpsertArgs< * The unique filter to find the record to update. */ where: WhereUniqueInput; -} & SelectIncludeOmit; +} & SelectIncludeOmit & + ExtractExtQueryArgs; type UpdateScalarInput< Schema extends SchemaDef, @@ -1745,17 +1776,20 @@ export type DeleteArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { /** * The unique filter to find the record to delete. */ where: WhereUniqueInput; -} & SelectIncludeOmit; +} & SelectIncludeOmit & + ExtractExtQueryArgs; export type DeleteManyArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { /** * Filter to select records to delete. @@ -1766,7 +1800,7 @@ export type DeleteManyArgs< * Limits the number of records to delete. */ limit?: number; -}; +} & ExtractExtQueryArgs; // #endregion @@ -1776,12 +1810,13 @@ export type CountArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = Omit, 'select' | 'include' | 'distinct' | 'omit'> & { /** * Selects fields to count */ select?: CountAggregateInput | true; -}; +} & ExtractExtQueryArgs; type CountAggregateInput> = { [Key in NonRelationFields]?: true; @@ -1805,6 +1840,7 @@ export type AggregateArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { /** * Filter conditions @@ -1851,7 +1887,8 @@ export type AggregateArgs< * Performs sum value aggregation. */ _sum?: SumAvgInput; - }); + }) & + ExtractExtQueryArgs; type NumericFields> = keyof { [Key in GetModelFields as GetModelFieldType extends @@ -1942,6 +1979,7 @@ export type GroupByArgs< Schema extends SchemaDef, Model extends GetModels, Options extends QueryOptions = QueryOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, > = { /** * Filter conditions @@ -1999,7 +2037,8 @@ export type GroupByArgs< * Performs sum value aggregation. */ _sum?: SumAvgInput; - }); + }) & + ExtractExtQueryArgs; export type GroupByResult< Schema extends SchemaDef, @@ -2346,4 +2385,28 @@ type ProviderSupportsDistinct = Schema['provider']['ty ? true : false; +/** + * Extracts extended query args for a specific operation. + */ +type ExtractExtQueryArgs = (Operation extends keyof ExtQueryArgs + ? ExtQueryArgs[Operation] + : {}) & + ('$create' extends keyof ExtQueryArgs + ? Operation extends CoreCreateOperations + ? ExtQueryArgs['$create'] + : {} + : {}) & + ('$read' extends keyof ExtQueryArgs ? (Operation extends CoreReadOperations ? ExtQueryArgs['$read'] : {}) : {}) & + ('$update' extends keyof ExtQueryArgs + ? Operation extends CoreUpdateOperations + ? ExtQueryArgs['$update'] + : {} + : {}) & + ('$delete' extends keyof ExtQueryArgs + ? Operation extends CoreDeleteOperations + ? ExtQueryArgs['$delete'] + : {} + : {}) & + ('$all' extends keyof ExtQueryArgs ? ExtQueryArgs['$all'] : {}); + // #endregion diff --git a/packages/orm/src/client/crud/operations/find.ts b/packages/orm/src/client/crud/operations/find.ts index 7bf56b8f5..197c1c643 100644 --- a/packages/orm/src/client/crud/operations/find.ts +++ b/packages/orm/src/client/crud/operations/find.ts @@ -10,7 +10,11 @@ export class FindOperationHandler extends BaseOperatio // parse args let parsedArgs = validateArgs - ? this.inputValidator.validateFindArgs(this.model, normalizedArgs, operation) + ? this.inputValidator.validateFindArgs( + this.model, + normalizedArgs, + operation as 'findFirst' | 'findUnique' | 'findMany', + ) : (normalizedArgs as any); if (findOne) { diff --git a/packages/orm/src/client/crud/validator/index.ts b/packages/orm/src/client/crud/validator/index.ts index 88bf0cfd1..cfd78eece 100644 --- a/packages/orm/src/client/crud/validator/index.ts +++ b/packages/orm/src/client/crud/validator/index.ts @@ -1,2254 +1 @@ -import { enumerate, invariant, lowerCaseFirst } from '@zenstackhq/common-helpers'; -import Decimal from 'decimal.js'; -import { match, P } from 'ts-pattern'; -import { z, ZodObject, ZodType } from 'zod'; -import { AnyNullClass, DbNullClass, JsonNullClass } from '../../../common-types'; -import { - type AttributeApplication, - type BuiltinType, - type FieldDef, - type GetModels, - type ProcedureDef, - type SchemaDef, -} from '../../../schema'; -import { extractFields } from '../../../utils/object-utils'; -import { formatError } from '../../../utils/zod-utils'; -import { AggregateOperators, FILTER_PROPERTY_TO_KIND, LOGICAL_COMBINATORS, NUMERIC_FIELD_TYPES } from '../../constants'; -import type { ClientContract } from '../../contract'; -import { - type AggregateArgs, - type CountArgs, - type CreateArgs, - type CreateManyAndReturnArgs, - type CreateManyArgs, - type DeleteArgs, - type DeleteManyArgs, - type ExistsArgs, - type FindArgs, - type GroupByArgs, - type UpdateArgs, - type UpdateManyAndReturnArgs, - type UpdateManyArgs, - type UpsertArgs, -} from '../../crud-types'; -import { createInternalError, createInvalidInputError } from '../../errors'; -import type { AnyPlugin } from '../../plugin'; -import { - fieldHasDefaultValue, - getDiscriminatorField, - getEnum, - getTypeDef, - getUniqueFields, - isEnum, - isTypeDef, - requireField, - requireModel, -} from '../../query-utils'; -import { - CoreCreateOperations, - CoreDeleteOperations, - CoreReadOperations, - CoreUpdateOperations, - type CoreCrudOperations, -} from '../operations/base'; -import { cache } from './cache-decorator'; -import { - addBigIntValidation, - addCustomValidation, - addDecimalValidation, - addListValidation, - addNumberValidation, - addStringValidation, -} from './utils'; - -type GetSchemaFunc = (model: GetModels) => ZodType; - -/** - * Minimal field information needed for filter schema generation. - */ -type FieldInfo = { - name: string; - type: string; - optional?: boolean; - array?: boolean; -}; - -export class InputValidator { - private readonly schemaCache = new Map(); - private readonly allFilterKinds = [...new Set(Object.values(FILTER_PROPERTY_TO_KIND))]; - - constructor(private readonly client: ClientContract) {} - - private get schema() { - return this.client.$schema; - } - - private get options() { - return this.client.$options; - } - - private get extraValidationsEnabled() { - return this.client.$options.validateInput !== false; - } - - // #region Entry points - - validateFindArgs( - model: GetModels, - args: unknown, - operation: CoreCrudOperations, - ): FindArgs, any, true> | undefined { - return this.validate, any, true> | undefined>( - model, - operation, - (model) => this.makeFindSchema(model, operation), - args, - ); - } - - validateExistsArgs( - model: GetModels, - args: unknown, - ): ExistsArgs, any> | undefined { - return this.validate, any> | undefined>( - model, - 'exists', - (model) => this.makeExistsSchema(model), - args, - ); - } - - validateCreateArgs(model: GetModels, args: unknown): CreateArgs, any> { - return this.validate, any>>( - model, - 'create', - (model) => this.makeCreateSchema(model), - args, - ); - } - - validateCreateManyArgs(model: GetModels, args: unknown): CreateManyArgs> { - return this.validate>>( - model, - 'createMany', - (model) => this.makeCreateManySchema(model), - args, - ); - } - - validateCreateManyAndReturnArgs( - model: GetModels, - args: unknown, - ): CreateManyAndReturnArgs, any> | undefined { - return this.validate, any> | undefined>( - model, - 'createManyAndReturn', - (model) => this.makeCreateManyAndReturnSchema(model), - args, - ); - } - - validateUpdateArgs(model: GetModels, args: unknown): UpdateArgs, any> { - return this.validate, any>>( - model, - 'update', - (model) => this.makeUpdateSchema(model), - args, - ); - } - - validateUpdateManyArgs(model: GetModels, args: unknown): UpdateManyArgs, any> { - return this.validate, any>>( - model, - 'updateMany', - (model) => this.makeUpdateManySchema(model), - args, - ); - } - - validateUpdateManyAndReturnArgs( - model: GetModels, - args: unknown, - ): UpdateManyAndReturnArgs, any> { - return this.validate, any>>( - model, - 'updateManyAndReturn', - (model) => this.makeUpdateManyAndReturnSchema(model), - args, - ); - } - - validateUpsertArgs(model: GetModels, args: unknown): UpsertArgs, any> { - return this.validate, any>>( - model, - 'upsert', - (model) => this.makeUpsertSchema(model), - args, - ); - } - - validateDeleteArgs(model: GetModels, args: unknown): DeleteArgs, any> { - return this.validate, any>>( - model, - 'delete', - (model) => this.makeDeleteSchema(model), - args, - ); - } - - validateDeleteManyArgs( - model: GetModels, - args: unknown, - ): DeleteManyArgs, any> | undefined { - return this.validate, any> | undefined>( - model, - 'deleteMany', - (model) => this.makeDeleteManySchema(model), - args, - ); - } - - validateCountArgs(model: GetModels, args: unknown): CountArgs, any> | undefined { - return this.validate, any> | undefined>( - model, - 'count', - (model) => this.makeCountSchema(model), - args, - ); - } - - validateAggregateArgs(model: GetModels, args: unknown): AggregateArgs, any> { - return this.validate, any>>( - model, - 'aggregate', - (model) => this.makeAggregateSchema(model), - args, - ); - } - - validateGroupByArgs(model: GetModels, args: unknown): GroupByArgs, any> { - return this.validate, any>>( - model, - 'groupBy', - (model) => this.makeGroupBySchema(model), - args, - ); - } - - // TODO: turn it into a Zod schema and cache - validateProcedureInput(proc: string, input: unknown): unknown { - const procDef = (this.schema.procedures ?? {})[proc] as ProcedureDef | undefined; - invariant(procDef, `Procedure "${proc}" not found in schema`); - - const params = Object.values(procDef.params ?? {}); - - // For procedures where every parameter is optional, allow omitting the input entirely. - if (typeof input === 'undefined') { - if (params.length === 0) { - return undefined; - } - if (params.every((p) => p.optional)) { - return undefined; - } - throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); - } - - if (typeof input !== 'object' || input === null || Array.isArray(input)) { - throw createInvalidInputError('Procedure input must be an object', `$procs.${proc}`); - } - - const envelope = input as Record; - const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') ? (envelope as any).args : undefined; - - if (params.length === 0) { - if (typeof argsPayload === 'undefined') { - return input; - } - if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { - throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); - } - if (Object.keys(argsPayload as any).length === 0) { - return input; - } - throw createInvalidInputError('Procedure does not accept arguments', `$procs.${proc}`); - } - - if (typeof argsPayload === 'undefined') { - if (params.every((p) => p.optional)) { - return input; - } - throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); - } - - if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { - throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); - } - - const obj = argsPayload as Record; - - for (const param of params) { - const value = (obj as any)[param.name]; - - if (!Object.prototype.hasOwnProperty.call(obj, param.name)) { - if (param.optional) { - continue; - } - throw createInvalidInputError(`Missing procedure argument: ${param.name}`, `$procs.${proc}`); - } - - if (typeof value === 'undefined') { - if (param.optional) { - continue; - } - throw createInvalidInputError( - `Invalid procedure argument: ${param.name} is required`, - `$procs.${proc}`, - ); - } - - const schema = this.makeProcedureParamSchema(param); - const parsed = schema.safeParse(value); - if (!parsed.success) { - throw createInvalidInputError( - `Invalid procedure argument: ${param.name}: ${formatError(parsed.error)}`, - `$procs.${proc}`, - ); - } - } - - return input; - } - - // #endregion - - // #region Validation helpers - - private validate(model: GetModels, operation: string, getSchema: GetSchemaFunc, args: unknown) { - const schema = getSchema(model); - const { error, data } = schema.safeParse(args); - if (error) { - throw createInvalidInputError( - `Invalid ${operation} args for model "${model}": ${formatError(error)}`, - model, - { - cause: error, - }, - ); - } - return data as T; - } - - private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) { - let result = schema; - for (const plugin of this.options.plugins ?? []) { - if (plugin.queryArgs) { - const pluginSchema = this.getPluginExtQueryArgsSchema(plugin, operation); - if (pluginSchema) { - result = result.extend(pluginSchema.shape); - } - } - } - return result.strict(); - } - - private getPluginExtQueryArgsSchema(plugin: AnyPlugin, operation: string): ZodObject | undefined { - if (!plugin.queryArgs) { - return undefined; - } - - let result: ZodType | undefined; - - if (operation in plugin.queryArgs && plugin.queryArgs[operation]) { - // most specific operation takes highest precedence - result = plugin.queryArgs[operation]; - } else if (operation === 'upsert') { - // upsert is special: it's in both CoreCreateOperations and CoreUpdateOperations - // so we need to merge both $create and $update schemas to match the type system - const createSchema = - '$create' in plugin.queryArgs && plugin.queryArgs['$create'] ? plugin.queryArgs['$create'] : undefined; - const updateSchema = - '$update' in plugin.queryArgs && plugin.queryArgs['$update'] ? plugin.queryArgs['$update'] : undefined; - - if (createSchema && updateSchema) { - invariant( - createSchema instanceof z.ZodObject, - 'Plugin extended query args schema must be a Zod object', - ); - invariant( - updateSchema instanceof z.ZodObject, - 'Plugin extended query args schema must be a Zod object', - ); - // merge both schemas (combines their properties) - result = createSchema.extend(updateSchema.shape); - } else if (createSchema) { - result = createSchema; - } else if (updateSchema) { - result = updateSchema; - } - } else if ( - // then comes grouped operations: $create, $read, $update, $delete - CoreCreateOperations.includes(operation as CoreCreateOperations) && - '$create' in plugin.queryArgs && - plugin.queryArgs['$create'] - ) { - result = plugin.queryArgs['$create']; - } else if ( - CoreReadOperations.includes(operation as CoreReadOperations) && - '$read' in plugin.queryArgs && - plugin.queryArgs['$read'] - ) { - result = plugin.queryArgs['$read']; - } else if ( - CoreUpdateOperations.includes(operation as CoreUpdateOperations) && - '$update' in plugin.queryArgs && - plugin.queryArgs['$update'] - ) { - result = plugin.queryArgs['$update']; - } else if ( - CoreDeleteOperations.includes(operation as CoreDeleteOperations) && - '$delete' in plugin.queryArgs && - plugin.queryArgs['$delete'] - ) { - result = plugin.queryArgs['$delete']; - } else if ('$all' in plugin.queryArgs && plugin.queryArgs['$all']) { - // finally comes $all - result = plugin.queryArgs['$all']; - } - - invariant( - result === undefined || result instanceof z.ZodObject, - 'Plugin extended query args schema must be a Zod object', - ); - return result; - } - - // #endregion - - // #region Find - - @cache() - private makeFindSchema(model: string, operation: CoreCrudOperations) { - const fields: Record = {}; - const unique = operation === 'findUnique'; - const findOne = operation === 'findUnique' || operation === 'findFirst'; - const where = this.makeWhereSchema(model, unique); - if (unique) { - fields['where'] = where; - } else { - fields['where'] = where.optional(); - } - - fields['select'] = this.makeSelectSchema(model).optional().nullable(); - fields['include'] = this.makeIncludeSchema(model).optional().nullable(); - fields['omit'] = this.makeOmitSchema(model).optional().nullable(); - - if (!unique) { - fields['skip'] = this.makeSkipSchema().optional(); - if (findOne) { - fields['take'] = z.literal(1).optional(); - } else { - fields['take'] = this.makeTakeSchema().optional(); - } - fields['orderBy'] = this.orArray(this.makeOrderBySchema(model, true, false), true).optional(); - fields['cursor'] = this.makeCursorSchema(model).optional(); - fields['distinct'] = this.makeDistinctSchema(model).optional(); - } - - const baseSchema = z.strictObject(fields); - let result: ZodType = this.mergePluginArgsSchema(baseSchema, operation); - result = this.refineForSelectIncludeMutuallyExclusive(result); - result = this.refineForSelectOmitMutuallyExclusive(result); - - if (!unique) { - result = result.optional(); - } - return result; - } - - @cache() - private makeExistsSchema(model: string) { - const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - }); - return this.mergePluginArgsSchema(baseSchema, 'exists').optional(); - } - - private makeScalarSchema(type: string, attributes?: readonly AttributeApplication[]) { - if (this.schema.typeDefs && type in this.schema.typeDefs) { - return this.makeTypeDefSchema(type); - } else if (this.schema.enums && type in this.schema.enums) { - return this.makeEnumSchema(type); - } else { - return match(type) - .with('String', () => - this.extraValidationsEnabled ? addStringValidation(z.string(), attributes) : z.string(), - ) - .with('Int', () => - this.extraValidationsEnabled ? addNumberValidation(z.number().int(), attributes) : z.number().int(), - ) - .with('Float', () => - this.extraValidationsEnabled ? addNumberValidation(z.number(), attributes) : z.number(), - ) - .with('Boolean', () => z.boolean()) - .with('BigInt', () => - z.union([ - this.extraValidationsEnabled - ? addNumberValidation(z.number().int(), attributes) - : z.number().int(), - this.extraValidationsEnabled ? addBigIntValidation(z.bigint(), attributes) : z.bigint(), - ]), - ) - .with('Decimal', () => { - return z.union([ - this.extraValidationsEnabled ? addNumberValidation(z.number(), attributes) : z.number(), - addDecimalValidation(z.instanceof(Decimal), attributes, this.extraValidationsEnabled), - addDecimalValidation(z.string(), attributes, this.extraValidationsEnabled), - ]); - }) - .with('DateTime', () => z.union([z.date(), z.iso.datetime()])) - .with('Bytes', () => z.instanceof(Uint8Array)) - .with('Json', () => this.makeJsonValueSchema(false, false)) - .otherwise(() => z.unknown()); - } - } - - @cache() - private makeEnumSchema(type: string) { - const enumDef = getEnum(this.schema, type); - invariant(enumDef, `Enum "${type}" not found in schema`); - return z.enum(Object.keys(enumDef.values) as [string, ...string[]]); - } - - @cache() - private makeTypeDefSchema(type: string): z.ZodType { - const typeDef = getTypeDef(this.schema, type); - invariant(typeDef, `Type definition "${type}" not found in schema`); - const schema = z.looseObject( - Object.fromEntries( - Object.entries(typeDef.fields).map(([field, def]) => { - let fieldSchema = this.makeScalarSchema(def.type); - if (def.array) { - fieldSchema = fieldSchema.array(); - } - if (def.optional) { - fieldSchema = fieldSchema.nullish(); - } - return [field, fieldSchema]; - }), - ), - ); - - // zod doesn't preserve object field order after parsing, here we use a - // validation-only custom schema and use the original data if parsing - // is successful - const finalSchema = z.any().superRefine((value, ctx) => { - const parseResult = schema.safeParse(value); - if (!parseResult.success) { - parseResult.error.issues.forEach((issue) => ctx.addIssue(issue as any)); - } - }); - - return finalSchema; - } - - @cache() - private makeWhereSchema( - model: string, - unique: boolean, - withoutRelationFields = false, - withAggregations = false, - ): ZodType { - const modelDef = requireModel(this.schema, model); - - // unique field used in unique filters bypass filter slicing - const uniqueFieldNames = unique - ? getUniqueFields(this.schema, model) - .filter( - (uf): uf is { name: string; def: FieldDef } => - // single-field unique - 'def' in uf, - ) - .map((uf) => uf.name) - : undefined; - - const fields: Record = {}; - for (const field of Object.keys(modelDef.fields)) { - const fieldDef = requireField(this.schema, model, field); - let fieldSchema: ZodType | undefined; - - if (fieldDef.relation) { - if (withoutRelationFields) { - continue; - } - - // Check if Relation filter kind is allowed - const allowedFilterKinds = this.getEffectiveFilterKinds(model, field); - if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) { - // Relation filters are not allowed for this field - use z.never() - fieldSchema = z.never(); - } else { - fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional()); - - // optional to-one relation allows null - fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional); - - if (fieldDef.array) { - // to-many relation - fieldSchema = z.union([ - fieldSchema, - z.strictObject({ - some: fieldSchema.optional(), - every: fieldSchema.optional(), - none: fieldSchema.optional(), - }), - ]); - } else { - // to-one relation - fieldSchema = z.union([ - fieldSchema, - z.strictObject({ - is: fieldSchema.optional(), - isNot: fieldSchema.optional(), - }), - ]); - } - } - } else { - const ignoreSlicing = !!uniqueFieldNames?.includes(field); - - const enumDef = getEnum(this.schema, fieldDef.type); - if (enumDef) { - // enum - if (Object.keys(enumDef.values).length > 0) { - fieldSchema = this.makeEnumFilterSchema(model, fieldDef, withAggregations, ignoreSlicing); - } - } else if (fieldDef.array) { - // array field - fieldSchema = this.makeArrayFilterSchema(model, fieldDef); - } else if (this.isTypeDefType(fieldDef.type)) { - fieldSchema = this.makeTypedJsonFilterSchema(model, fieldDef); - } else { - // primitive field - fieldSchema = this.makePrimitiveFilterSchema(model, fieldDef, withAggregations, ignoreSlicing); - } - } - - if (fieldSchema) { - fields[field] = fieldSchema.optional(); - } - } - - if (unique) { - // add compound unique fields, e.g. `{ id1_id2: { id1: 1, id2: 1 } }` - // compound-field filters are not affected by slicing - const uniqueFields = getUniqueFields(this.schema, model); - for (const uniqueField of uniqueFields) { - if ('defs' in uniqueField) { - fields[uniqueField.name] = z - .object( - Object.fromEntries( - Object.entries(uniqueField.defs).map(([key, def]) => { - invariant(!def.relation, 'unique field cannot be a relation'); - let fieldSchema: ZodType; - const enumDef = getEnum(this.schema, def.type); - if (enumDef) { - // enum - if (Object.keys(enumDef.values).length > 0) { - fieldSchema = this.makeEnumFilterSchema(model, def, false, true); - } else { - fieldSchema = z.never(); - } - } else { - fieldSchema = this.makePrimitiveFilterSchema(model, def, false, true); - } - return [key, fieldSchema]; - }), - ), - ) - .optional(); - } - } - } - - // expression builder - fields['$expr'] = z.custom((v) => typeof v === 'function', { error: '"$expr" must be a function' }).optional(); - - // logical operators - fields['AND'] = this.orArray( - z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)), - true, - ).optional(); - fields['OR'] = z - .lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)) - .array() - .optional(); - fields['NOT'] = this.orArray( - z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)), - true, - ).optional(); - - const baseWhere = z.strictObject(fields); - let result: ZodType = baseWhere; - - if (unique) { - // requires at least one unique field (field set) is required - const uniqueFields = getUniqueFields(this.schema, model); - if (uniqueFields.length === 0) { - throw createInternalError(`Model "${model}" has no unique fields`); - } - - if (uniqueFields.length === 1) { - // only one unique field (set), mark the field(s) required - result = baseWhere.required({ - [uniqueFields[0]!.name]: true, - } as any); - } else { - result = baseWhere.refine((value) => { - // check that at least one unique field is set - return uniqueFields.some(({ name }) => value[name] !== undefined); - }, `At least one unique field or field set must be set`); - } - } - - return result; - } - - @cache() - private makeTypedJsonFilterSchema(contextModel: string | undefined, fieldInfo: FieldInfo) { - const field = fieldInfo.name; - const type = fieldInfo.type; - const optional = !!fieldInfo.optional; - const array = !!fieldInfo.array; - - const typeDef = getTypeDef(this.schema, type); - invariant(typeDef, `Type definition "${type}" not found in schema`); - - const candidates: z.ZodType[] = []; - - if (!array) { - // fields filter - const fieldSchemas: Record = {}; - for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { - if (this.isTypeDefType(fieldDef.type)) { - // recursive typed JSON - use same model/field for nested typed JSON - fieldSchemas[fieldName] = this.makeTypedJsonFilterSchema(contextModel, fieldDef).optional(); - } else { - // enum, array, primitives - const enumDef = getEnum(this.schema, fieldDef.type); - if (enumDef) { - fieldSchemas[fieldName] = this.makeEnumFilterSchema(contextModel, fieldDef, false).optional(); - } else if (fieldDef.array) { - fieldSchemas[fieldName] = this.makeArrayFilterSchema(contextModel, fieldDef).optional(); - } else { - fieldSchemas[fieldName] = this.makePrimitiveFilterSchema( - contextModel, - fieldDef, - false, - ).optional(); - } - } - } - - candidates.push(z.strictObject(fieldSchemas)); - } - - const recursiveSchema = z - .lazy(() => this.makeTypedJsonFilterSchema(contextModel, { name: field, type, optional, array: false })) - .optional(); - if (array) { - // array filter - candidates.push( - z.strictObject({ - some: recursiveSchema, - every: recursiveSchema, - none: recursiveSchema, - }), - ); - } else { - // is / isNot filter - candidates.push( - z.strictObject({ - is: recursiveSchema, - isNot: recursiveSchema, - }), - ); - } - - // plain json filter - candidates.push(this.makeJsonFilterSchema(contextModel, field, optional)); - - if (optional) { - // allow null as well - candidates.push(z.null()); - } - - // either plain json filter or field filters - return z.union(candidates); - } - - private isTypeDefType(type: string) { - return this.schema.typeDefs && type in this.schema.typeDefs; - } - - @cache() - private makeEnumFilterSchema( - model: string | undefined, - fieldInfo: FieldInfo, - withAggregations: boolean, - ignoreSlicing: boolean = false, - ) { - const enumName = fieldInfo.type; - const optional = !!fieldInfo.optional; - const array = !!fieldInfo.array; - - const enumDef = getEnum(this.schema, enumName); - invariant(enumDef, `Enum "${enumName}" not found in schema`); - const baseSchema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); - if (array) { - return this.internalMakeArrayFilterSchema(model, fieldInfo.name, baseSchema); - } - const allowedFilterKinds = ignoreSlicing ? undefined : this.getEffectiveFilterKinds(model, fieldInfo.name); - const components = this.makeCommonPrimitiveFilterComponents( - baseSchema, - optional, - () => z.lazy(() => this.makeEnumFilterSchema(model, fieldInfo, withAggregations)), - ['equals', 'in', 'notIn', 'not'], - withAggregations ? ['_count', '_min', '_max'] : undefined, - allowedFilterKinds, - ); - - return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); - } - - @cache() - private makeArrayFilterSchema(model: string | undefined, fieldInfo: FieldInfo) { - return this.internalMakeArrayFilterSchema( - model, - fieldInfo.name, - this.makeScalarSchema(fieldInfo.type as BuiltinType), - ); - } - - private internalMakeArrayFilterSchema(contextModel: string | undefined, field: string, elementSchema: ZodType) { - const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field); - const operators = { - equals: elementSchema.array().optional(), - has: elementSchema.optional(), - hasEvery: elementSchema.array().optional(), - hasSome: elementSchema.array().optional(), - isEmpty: z.boolean().optional(), - }; - - // Filter operators based on allowed filter kinds - const filteredOperators = this.trimFilterOperators(operators, allowedFilterKinds); - - return z.strictObject(filteredOperators); - } - - @cache() - private makePrimitiveFilterSchema( - contextModel: string | undefined, - fieldInfo: FieldInfo, - withAggregations: boolean, - ignoreSlicing = false, - ) { - const allowedFilterKinds = ignoreSlicing - ? undefined - : this.getEffectiveFilterKinds(contextModel, fieldInfo.name); - const type = fieldInfo.type as BuiltinType; - const optional = !!fieldInfo.optional; - return match(type) - .with('String', () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)) - .with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) => - this.makeNumberFilterSchema( - this.makeScalarSchema(type), - optional, - withAggregations, - allowedFilterKinds, - ), - ) - .with('Boolean', () => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)) - .with('DateTime', () => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)) - .with('Bytes', () => this.makeBytesFilterSchema(optional, withAggregations, allowedFilterKinds)) - .with('Json', () => this.makeJsonFilterSchema(contextModel, fieldInfo.name, optional)) - .with('Unsupported', () => z.never()) - .exhaustive(); - } - - private makeJsonValueSchema(nullable: boolean, forFilter: boolean): z.ZodType { - const options: z.ZodType[] = [z.string(), z.number(), z.boolean(), z.instanceof(JsonNullClass)]; - - if (forFilter) { - options.push(z.instanceof(DbNullClass)); - } else { - if (nullable) { - // for mutation, allow DbNull only if nullable - options.push(z.instanceof(DbNullClass)); - } - } - - if (forFilter) { - options.push(z.instanceof(AnyNullClass)); - } - - const schema = z.union([ - ...options, - z.lazy(() => z.union([this.makeJsonValueSchema(false, false), z.null()]).array()), - z.record( - z.string(), - z.lazy(() => z.union([this.makeJsonValueSchema(false, false), z.null()])), - ), - ]); - return this.nullableIf(schema, nullable); - } - - @cache() - private makeJsonFilterSchema(contextModel: string | undefined, field: string, optional: boolean) { - const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field); - - // Check if Json filter kind is allowed - if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) { - // Return a never schema if Json filters are not allowed - return z.never(); - } - - const valueSchema = this.makeJsonValueSchema(optional, true); - return z.strictObject({ - path: z.string().optional(), - equals: valueSchema.optional(), - not: valueSchema.optional(), - string_contains: z.string().optional(), - string_starts_with: z.string().optional(), - string_ends_with: z.string().optional(), - mode: this.makeStringModeSchema().optional(), - array_contains: valueSchema.optional(), - array_starts_with: valueSchema.optional(), - array_ends_with: valueSchema.optional(), - }); - } - - @cache() - private makeDateTimeFilterSchema( - optional: boolean, - withAggregations: boolean, - allowedFilterKinds: string[] | undefined, - ): ZodType { - return this.makeCommonPrimitiveFilterSchema( - z.union([z.iso.datetime(), z.date()]), - optional, - () => z.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)), - withAggregations ? ['_count', '_min', '_max'] : undefined, - allowedFilterKinds, - ); - } - - @cache() - private makeBooleanFilterSchema( - optional: boolean, - withAggregations: boolean, - allowedFilterKinds: string[] | undefined, - ): ZodType { - const components = this.makeCommonPrimitiveFilterComponents( - z.boolean(), - optional, - () => z.lazy(() => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)), - ['equals', 'not'], - withAggregations ? ['_count', '_min', '_max'] : undefined, - allowedFilterKinds, - ); - - return this.createUnionFilterSchema(z.boolean(), optional, components, allowedFilterKinds); - } - - @cache() - private makeBytesFilterSchema( - optional: boolean, - withAggregations: boolean, - allowedFilterKinds: string[] | undefined, - ): ZodType { - const baseSchema = z.instanceof(Uint8Array); - const components = this.makeCommonPrimitiveFilterComponents( - baseSchema, - optional, - () => z.instanceof(Uint8Array), - ['equals', 'in', 'notIn', 'not'], - withAggregations ? ['_count', '_min', '_max'] : undefined, - allowedFilterKinds, - ); - - return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); - } - - private makeCommonPrimitiveFilterComponents( - baseSchema: ZodType, - optional: boolean, - makeThis: () => ZodType, - supportedOperators: string[] | undefined = undefined, - withAggregations: Array<'_count' | '_avg' | '_sum' | '_min' | '_max'> | undefined = undefined, - allowedFilterKinds: string[] | undefined = undefined, - ) { - const commonAggSchema = () => - this.makeCommonPrimitiveFilterSchema(baseSchema, false, makeThis, undefined, allowedFilterKinds).optional(); - let result = { - equals: this.nullableIf(baseSchema.optional(), optional), - in: baseSchema.array().optional(), - notIn: baseSchema.array().optional(), - lt: baseSchema.optional(), - lte: baseSchema.optional(), - gt: baseSchema.optional(), - gte: baseSchema.optional(), - between: baseSchema.array().length(2).optional(), - not: makeThis().optional(), - ...(withAggregations?.includes('_count') - ? { _count: this.makeNumberFilterSchema(z.number().int(), false, false, undefined).optional() } - : {}), - ...(withAggregations?.includes('_avg') ? { _avg: commonAggSchema() } : {}), - ...(withAggregations?.includes('_sum') ? { _sum: commonAggSchema() } : {}), - ...(withAggregations?.includes('_min') ? { _min: commonAggSchema() } : {}), - ...(withAggregations?.includes('_max') ? { _max: commonAggSchema() } : {}), - }; - if (supportedOperators) { - const keys = [...supportedOperators, ...(withAggregations ?? [])]; - result = extractFields(result, keys) as typeof result; - } - - // Filter operators based on allowed filter kinds - result = this.trimFilterOperators(result, allowedFilterKinds) as typeof result; - - return result; - } - - private makeCommonPrimitiveFilterSchema( - baseSchema: ZodType, - optional: boolean, - makeThis: () => ZodType, - withAggregations: Array | undefined = undefined, - allowedFilterKinds: string[] | undefined = undefined, - ): z.ZodType { - const components = this.makeCommonPrimitiveFilterComponents( - baseSchema, - optional, - makeThis, - undefined, - withAggregations, - allowedFilterKinds, - ); - - return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); - } - - private makeNumberFilterSchema( - baseSchema: ZodType, - optional: boolean, - withAggregations: boolean, - allowedFilterKinds: string[] | undefined, - ): ZodType { - return this.makeCommonPrimitiveFilterSchema( - baseSchema, - optional, - () => z.lazy(() => this.makeNumberFilterSchema(baseSchema, optional, withAggregations, allowedFilterKinds)), - withAggregations ? ['_count', '_avg', '_sum', '_min', '_max'] : undefined, - allowedFilterKinds, - ); - } - - private makeStringFilterSchema( - optional: boolean, - withAggregations: boolean, - allowedFilterKinds: string[] | undefined, - ): ZodType { - const baseComponents = this.makeCommonPrimitiveFilterComponents( - z.string(), - optional, - () => z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)), - undefined, - withAggregations ? ['_count', '_min', '_max'] : undefined, - allowedFilterKinds, - ); - - const stringSpecificOperators = { - startsWith: z.string().optional(), - endsWith: z.string().optional(), - contains: z.string().optional(), - ...(this.providerSupportsCaseSensitivity - ? { - mode: this.makeStringModeSchema().optional(), - } - : {}), - }; - - // Filter string-specific operators based on allowed filter kinds - const filteredStringOperators = this.trimFilterOperators(stringSpecificOperators, allowedFilterKinds); - - const allComponents = { - ...baseComponents, - ...filteredStringOperators, - }; - - return this.createUnionFilterSchema(z.string(), optional, allComponents, allowedFilterKinds); - } - - private makeStringModeSchema() { - return z.union([z.literal('default'), z.literal('insensitive')]); - } - - @cache() - private makeSelectSchema(model: string) { - const modelDef = requireModel(this.schema, model); - const fields: Record = {}; - for (const field of Object.keys(modelDef.fields)) { - const fieldDef = requireField(this.schema, model, field); - if (fieldDef.relation) { - // Check if the target model is allowed by slicing configuration - if (this.isModelAllowed(fieldDef.type)) { - fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); - } - } else { - fields[field] = z.boolean().optional(); - } - } - - const _countSchema = this.makeCountSelectionSchema(model); - if (!(_countSchema instanceof z.ZodNever)) { - fields['_count'] = _countSchema; - } - - return z.strictObject(fields); - } - - @cache() - private makeCountSelectionSchema(model: string) { - const modelDef = requireModel(this.schema, model); - const toManyRelations = Object.values(modelDef.fields).filter((def) => def.relation && def.array); - if (toManyRelations.length > 0) { - return z - .union([ - z.literal(true), - z.strictObject({ - select: z.strictObject( - toManyRelations.reduce( - (acc, fieldDef) => ({ - ...acc, - [fieldDef.name]: z - .union([ - z.boolean(), - z.strictObject({ - where: this.makeWhereSchema(fieldDef.type, false, false), - }), - ]) - .optional(), - }), - {} as Record, - ), - ), - }), - ]) - .optional(); - } else { - return z.never(); - } - } - - @cache() - private makeRelationSelectIncludeSchema(model: string, field: string) { - const fieldDef = requireField(this.schema, model, field); - let objSchema: z.ZodType = z.strictObject({ - ...(fieldDef.array || fieldDef.optional - ? { - // to-many relations and optional to-one relations are filterable - where: z.lazy(() => this.makeWhereSchema(fieldDef.type, false)).optional(), - } - : {}), - select: z - .lazy(() => this.makeSelectSchema(fieldDef.type)) - .optional() - .nullable(), - include: z - .lazy(() => this.makeIncludeSchema(fieldDef.type)) - .optional() - .nullable(), - omit: z - .lazy(() => this.makeOmitSchema(fieldDef.type)) - .optional() - .nullable(), - ...(fieldDef.array - ? { - // to-many relations can be ordered, skipped, taken, and cursor-located - orderBy: z - .lazy(() => this.orArray(this.makeOrderBySchema(fieldDef.type, true, false), true)) - .optional(), - skip: this.makeSkipSchema().optional(), - take: this.makeTakeSchema().optional(), - cursor: this.makeCursorSchema(fieldDef.type).optional(), - distinct: this.makeDistinctSchema(fieldDef.type).optional(), - } - : {}), - }); - - objSchema = this.refineForSelectIncludeMutuallyExclusive(objSchema); - objSchema = this.refineForSelectOmitMutuallyExclusive(objSchema); - - return z.union([z.boolean(), objSchema]); - } - - @cache() - private makeOmitSchema(model: string) { - const modelDef = requireModel(this.schema, model); - const fields: Record = {}; - for (const field of Object.keys(modelDef.fields)) { - const fieldDef = requireField(this.schema, model, field); - if (!fieldDef.relation) { - if (this.options.allowQueryTimeOmitOverride !== false) { - // if override is allowed, use boolean - fields[field] = z.boolean().optional(); - } else { - // otherwise only allow true - fields[field] = z.literal(true).optional(); - } - } - } - return z.strictObject(fields); - } - - @cache() - private makeIncludeSchema(model: string) { - const modelDef = requireModel(this.schema, model); - const fields: Record = {}; - for (const field of Object.keys(modelDef.fields)) { - const fieldDef = requireField(this.schema, model, field); - if (fieldDef.relation) { - // Check if the target model is allowed by slicing configuration - if (this.isModelAllowed(fieldDef.type)) { - fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); - } - } - } - - const _countSchema = this.makeCountSelectionSchema(model); - if (!(_countSchema instanceof z.ZodNever)) { - fields['_count'] = _countSchema; - } - - return z.strictObject(fields); - } - - @cache() - private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) { - const modelDef = requireModel(this.schema, model); - const fields: Record = {}; - const sort = z.union([z.literal('asc'), z.literal('desc')]); - for (const field of Object.keys(modelDef.fields)) { - const fieldDef = requireField(this.schema, model, field); - if (fieldDef.relation) { - // relations - if (withRelation) { - fields[field] = z.lazy(() => { - let relationOrderBy = this.makeOrderBySchema(fieldDef.type, withRelation, WithAggregation); - if (fieldDef.array) { - relationOrderBy = relationOrderBy.extend({ - _count: sort, - }); - } - return relationOrderBy.optional(); - }); - } - } else { - // scalars - if (fieldDef.optional) { - fields[field] = z - .union([ - sort, - z.strictObject({ - sort, - nulls: z.union([z.literal('first'), z.literal('last')]), - }), - ]) - .optional(); - } else { - fields[field] = sort.optional(); - } - } - } - - // aggregations - if (WithAggregation) { - const aggregationFields = ['_count', '_avg', '_sum', '_min', '_max']; - for (const agg of aggregationFields) { - fields[agg] = z.lazy(() => this.makeOrderBySchema(model, true, false).optional()); - } - } - - return z.strictObject(fields); - } - - @cache() - private makeDistinctSchema(model: string) { - const modelDef = requireModel(this.schema, model); - const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); - return this.orArray(z.enum(nonRelationFields as any), true); - } - - private makeCursorSchema(model: string) { - // `makeWhereSchema` is already cached - return this.makeWhereSchema(model, true, true).optional(); - } - - // #endregion - - // #region Create - - @cache() - private makeCreateSchema(model: string) { - const dataSchema = this.makeCreateDataSchema(model, false); - const baseSchema = z.strictObject({ - data: dataSchema, - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), - omit: this.makeOmitSchema(model).optional().nullable(), - }); - let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'create'); - schema = this.refineForSelectIncludeMutuallyExclusive(schema); - schema = this.refineForSelectOmitMutuallyExclusive(schema); - return schema; - } - - @cache() - private makeCreateManySchema(model: string) { - return this.mergePluginArgsSchema(this.makeCreateManyDataSchema(model, []), 'createMany').optional(); - } - - @cache() - private makeCreateManyAndReturnSchema(model: string) { - const base = this.makeCreateManyDataSchema(model, []); - let result: ZodObject = base.extend({ - select: this.makeSelectSchema(model).optional().nullable(), - omit: this.makeOmitSchema(model).optional().nullable(), - }); - result = this.mergePluginArgsSchema(result, 'createManyAndReturn'); - return this.refineForSelectOmitMutuallyExclusive(result).optional(); - } - - @cache() - private makeCreateDataSchema( - model: string, - canBeArray: boolean, - withoutFields: string[] = [], - withoutRelationFields = false, - ) { - const uncheckedVariantFields: Record = {}; - const checkedVariantFields: Record = {}; - const modelDef = requireModel(this.schema, model); - const hasRelation = - !withoutRelationFields && - Object.entries(modelDef.fields).some(([f, def]) => !withoutFields.includes(f) && def.relation); - - Object.keys(modelDef.fields).forEach((field) => { - if (withoutFields.includes(field)) { - return; - } - const fieldDef = requireField(this.schema, model, field); - if (fieldDef.computed) { - return; - } - - if (this.isDelegateDiscriminator(fieldDef)) { - // discriminator field is auto-assigned - return; - } - - if (fieldDef.relation) { - if (withoutRelationFields) { - return; - } - // Check if the target model is allowed by slicing configuration - if (!this.isModelAllowed(fieldDef.type)) { - return; - } - const excludeFields: string[] = []; - const oppositeField = fieldDef.relation.opposite; - if (oppositeField) { - excludeFields.push(oppositeField); - const oppositeFieldDef = requireField(this.schema, fieldDef.type, oppositeField); - if (oppositeFieldDef.relation?.fields) { - excludeFields.push(...oppositeFieldDef.relation.fields); - } - } - - let fieldSchema: ZodType = z.lazy(() => - this.makeRelationManipulationSchema(model, field, excludeFields, 'create'), - ); - - if (fieldDef.optional || fieldDef.array) { - // optional or array relations are optional - fieldSchema = fieldSchema.optional(); - } else { - // if all fk fields are optional, the relation is optional - let allFksOptional = false; - if (fieldDef.relation.fields) { - allFksOptional = fieldDef.relation.fields.every((f) => { - const fkDef = requireField(this.schema, model, f); - return fkDef.optional || fieldHasDefaultValue(fkDef); - }); - } - if (allFksOptional) { - fieldSchema = fieldSchema.optional(); - } - } - - // optional to-one relation can be null - if (fieldDef.optional && !fieldDef.array) { - fieldSchema = fieldSchema.nullable(); - } - checkedVariantFields[field] = fieldSchema; - if (fieldDef.array || !fieldDef.relation.references) { - // non-owned relation - uncheckedVariantFields[field] = fieldSchema; - } - } else { - let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes); - - if (fieldDef.array) { - fieldSchema = addListValidation(fieldSchema.array(), fieldDef.attributes); - fieldSchema = z - .union([ - fieldSchema, - z.strictObject({ - set: fieldSchema, - }), - ]) - .optional(); - } - - if (fieldDef.optional || fieldHasDefaultValue(fieldDef)) { - fieldSchema = fieldSchema.optional(); - } - - if (fieldDef.optional) { - if (fieldDef.type === 'Json') { - // DbNull for Json fields - fieldSchema = z.union([fieldSchema, z.instanceof(DbNullClass)]); - } else { - fieldSchema = fieldSchema.nullable(); - } - } - - uncheckedVariantFields[field] = fieldSchema; - if (!fieldDef.foreignKeyFor) { - // non-fk field - checkedVariantFields[field] = fieldSchema; - } - } - }); - - const uncheckedCreateSchema = this.extraValidationsEnabled - ? addCustomValidation(z.strictObject(uncheckedVariantFields), modelDef.attributes) - : z.strictObject(uncheckedVariantFields); - const checkedCreateSchema = this.extraValidationsEnabled - ? addCustomValidation(z.strictObject(checkedVariantFields), modelDef.attributes) - : z.strictObject(checkedVariantFields); - - if (!hasRelation) { - return this.orArray(uncheckedCreateSchema, canBeArray); - } else { - return z.union([ - uncheckedCreateSchema, - checkedCreateSchema, - ...(canBeArray ? [z.array(uncheckedCreateSchema)] : []), - ...(canBeArray ? [z.array(checkedCreateSchema)] : []), - ]); - } - } - - private isDelegateDiscriminator(fieldDef: FieldDef) { - if (!fieldDef.originModel) { - // not inherited from a delegate - return false; - } - const discriminatorField = getDiscriminatorField(this.schema, fieldDef.originModel); - return discriminatorField === fieldDef.name; - } - - @cache() - private makeRelationManipulationSchema( - model: string, - field: string, - withoutFields: string[], - mode: 'create' | 'update', - ) { - const fieldDef = requireField(this.schema, model, field); - const fieldType = fieldDef.type; - const array = !!fieldDef.array; - const fields: Record = { - create: this.makeCreateDataSchema(fieldDef.type, !!fieldDef.array, withoutFields).optional(), - - connect: this.makeConnectDataSchema(fieldType, array).optional(), - - connectOrCreate: this.makeConnectOrCreateDataSchema(fieldType, array, withoutFields).optional(), - }; - - if (array) { - fields['createMany'] = this.makeCreateManyDataSchema(fieldType, withoutFields).optional(); - } - - if (mode === 'update') { - if (fieldDef.optional || fieldDef.array) { - // disconnect and delete are only available for optional/to-many relations - fields['disconnect'] = this.makeDisconnectDataSchema(fieldType, array).optional(); - - fields['delete'] = this.makeDeleteRelationDataSchema(fieldType, array, true).optional(); - } - - fields['update'] = array - ? this.orArray( - z.strictObject({ - where: this.makeWhereSchema(fieldType, true), - data: this.makeUpdateDataSchema(fieldType, withoutFields), - }), - true, - ).optional() - : z - .union([ - z.strictObject({ - where: this.makeWhereSchema(fieldType, false).optional(), - data: this.makeUpdateDataSchema(fieldType, withoutFields), - }), - this.makeUpdateDataSchema(fieldType, withoutFields), - ]) - .optional(); - - let upsertWhere = this.makeWhereSchema(fieldType, true); - if (!fieldDef.array) { - // to-one relation, can upsert without where clause - upsertWhere = upsertWhere.optional(); - } - fields['upsert'] = this.orArray( - z.strictObject({ - where: upsertWhere, - create: this.makeCreateDataSchema(fieldType, false, withoutFields), - update: this.makeUpdateDataSchema(fieldType, withoutFields), - }), - true, - ).optional(); - - if (array) { - // to-many relation specifics - fields['set'] = this.makeSetDataSchema(fieldType, true).optional(); - - fields['updateMany'] = this.orArray( - z.strictObject({ - where: this.makeWhereSchema(fieldType, false, true), - data: this.makeUpdateDataSchema(fieldType, withoutFields), - }), - true, - ).optional(); - - fields['deleteMany'] = this.makeDeleteRelationDataSchema(fieldType, true, false).optional(); - } - } - - return z.strictObject(fields); - } - - @cache() - private makeSetDataSchema(model: string, canBeArray: boolean) { - return this.orArray(this.makeWhereSchema(model, true), canBeArray); - } - - @cache() - private makeConnectDataSchema(model: string, canBeArray: boolean) { - return this.orArray(this.makeWhereSchema(model, true), canBeArray); - } - - @cache() - private makeDisconnectDataSchema(model: string, canBeArray: boolean) { - if (canBeArray) { - // to-many relation, must be unique filters - return this.orArray(this.makeWhereSchema(model, true), canBeArray); - } else { - // to-one relation, can be boolean or a regular filter - the entity - // being disconnected is already uniquely identified by its parent - return z.union([z.boolean(), this.makeWhereSchema(model, false)]); - } - } - - @cache() - private makeDeleteRelationDataSchema(model: string, toManyRelation: boolean, uniqueFilter: boolean) { - return toManyRelation - ? this.orArray(this.makeWhereSchema(model, uniqueFilter), true) - : z.union([z.boolean(), this.makeWhereSchema(model, uniqueFilter)]); - } - - @cache() - private makeConnectOrCreateDataSchema(model: string, canBeArray: boolean, withoutFields: string[]) { - const whereSchema = this.makeWhereSchema(model, true); - const createSchema = this.makeCreateDataSchema(model, false, withoutFields); - return this.orArray( - z.strictObject({ - where: whereSchema, - create: createSchema, - }), - canBeArray, - ); - } - - @cache() - private makeCreateManyDataSchema(model: string, withoutFields: string[]) { - return z.strictObject({ - data: this.makeCreateDataSchema(model, true, withoutFields, true), - skipDuplicates: z.boolean().optional(), - }); - } - - // #endregion - - // #region Update - - @cache() - private makeUpdateSchema(model: string) { - const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, true), - data: this.makeUpdateDataSchema(model), - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), - omit: this.makeOmitSchema(model).optional().nullable(), - }); - let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'update'); - schema = this.refineForSelectIncludeMutuallyExclusive(schema); - schema = this.refineForSelectOmitMutuallyExclusive(schema); - return schema; - } - - @cache() - private makeUpdateManySchema(model: string) { - return this.mergePluginArgsSchema( - z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - data: this.makeUpdateDataSchema(model, [], true), - limit: z.number().int().nonnegative().optional(), - }), - 'updateMany', - ); - } - - @cache() - private makeUpdateManyAndReturnSchema(model: string) { - // plugin extended args schema is merged in `makeUpdateManySchema` - const baseSchema: ZodObject = this.makeUpdateManySchema(model); - let schema: ZodType = baseSchema.extend({ - select: this.makeSelectSchema(model).optional().nullable(), - omit: this.makeOmitSchema(model).optional().nullable(), - }); - schema = this.refineForSelectOmitMutuallyExclusive(schema); - return schema; - } - - @cache() - private makeUpsertSchema(model: string) { - const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, true), - create: this.makeCreateDataSchema(model, false), - update: this.makeUpdateDataSchema(model), - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), - omit: this.makeOmitSchema(model).optional().nullable(), - }); - let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'upsert'); - schema = this.refineForSelectIncludeMutuallyExclusive(schema); - schema = this.refineForSelectOmitMutuallyExclusive(schema); - return schema; - } - - @cache() - private makeUpdateDataSchema(model: string, withoutFields: string[] = [], withoutRelationFields = false) { - const uncheckedVariantFields: Record = {}; - const checkedVariantFields: Record = {}; - const modelDef = requireModel(this.schema, model); - const hasRelation = Object.entries(modelDef.fields).some( - ([key, value]) => value.relation && !withoutFields.includes(key), - ); - - Object.keys(modelDef.fields).forEach((field) => { - if (withoutFields.includes(field)) { - return; - } - const fieldDef = requireField(this.schema, model, field); - - if (fieldDef.relation) { - if (withoutRelationFields) { - return; - } - // Check if the target model is allowed by slicing configuration - if (!this.isModelAllowed(fieldDef.type)) { - return; - } - const excludeFields: string[] = []; - const oppositeField = fieldDef.relation.opposite; - if (oppositeField) { - excludeFields.push(oppositeField); - const oppositeFieldDef = requireField(this.schema, fieldDef.type, oppositeField); - if (oppositeFieldDef.relation?.fields) { - excludeFields.push(...oppositeFieldDef.relation.fields); - } - } - let fieldSchema: ZodType = z - .lazy(() => this.makeRelationManipulationSchema(model, field, excludeFields, 'update')) - .optional(); - // optional to-one relation can be null - if (fieldDef.optional && !fieldDef.array) { - fieldSchema = fieldSchema.nullable(); - } - checkedVariantFields[field] = fieldSchema; - if (fieldDef.array || !fieldDef.relation.references) { - // non-owned relation - uncheckedVariantFields[field] = fieldSchema; - } - } else { - let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes); - - if (this.isNumericField(fieldDef)) { - fieldSchema = z.union([ - fieldSchema, - z - .object({ - set: this.nullableIf(z.number().optional(), !!fieldDef.optional).optional(), - increment: z.number().optional(), - decrement: z.number().optional(), - multiply: z.number().optional(), - divide: z.number().optional(), - }) - .refine( - (v) => Object.keys(v).length === 1, - 'Only one of "set", "increment", "decrement", "multiply", or "divide" can be provided', - ), - ]); - } - - if (fieldDef.array) { - const arraySchema = addListValidation(fieldSchema.array(), fieldDef.attributes); - fieldSchema = z.union([ - arraySchema, - z - .object({ - set: arraySchema.optional(), - push: z.union([fieldSchema, fieldSchema.array()]).optional(), - }) - .refine((v) => Object.keys(v).length === 1, 'Only one of "set", "push" can be provided'), - ]); - } - - if (fieldDef.optional) { - if (fieldDef.type === 'Json') { - // DbNull for Json fields - fieldSchema = z.union([fieldSchema, z.instanceof(DbNullClass)]); - } else { - fieldSchema = fieldSchema.nullable(); - } - } - - // all fields are optional in update - fieldSchema = fieldSchema.optional(); - - uncheckedVariantFields[field] = fieldSchema; - if (!fieldDef.foreignKeyFor) { - // non-fk field - checkedVariantFields[field] = fieldSchema; - } - } - }); - - const uncheckedUpdateSchema = this.extraValidationsEnabled - ? addCustomValidation(z.strictObject(uncheckedVariantFields), modelDef.attributes) - : z.strictObject(uncheckedVariantFields); - const checkedUpdateSchema = this.extraValidationsEnabled - ? addCustomValidation(z.strictObject(checkedVariantFields), modelDef.attributes) - : z.strictObject(checkedVariantFields); - if (!hasRelation) { - return uncheckedUpdateSchema; - } else { - return z.union([uncheckedUpdateSchema, checkedUpdateSchema]); - } - } - - // #endregion - - // #region Delete - - @cache() - private makeDeleteSchema(model: string) { - const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, true), - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), - omit: this.makeOmitSchema(model).optional().nullable(), - }); - let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'delete'); - schema = this.refineForSelectIncludeMutuallyExclusive(schema); - schema = this.refineForSelectOmitMutuallyExclusive(schema); - return schema; - } - - @cache() - private makeDeleteManySchema(model: string) { - return this.mergePluginArgsSchema( - z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - limit: z.number().int().nonnegative().optional(), - }), - 'deleteMany', - ).optional(); - } - - // #endregion - - // #region Count - - @cache() - makeCountSchema(model: string) { - return this.mergePluginArgsSchema( - z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - skip: this.makeSkipSchema().optional(), - take: this.makeTakeSchema().optional(), - orderBy: this.orArray(this.makeOrderBySchema(model, true, false), true).optional(), - select: this.makeCountAggregateInputSchema(model).optional(), - }), - 'count', - ).optional(); - } - - @cache() - private makeCountAggregateInputSchema(model: string) { - const modelDef = requireModel(this.schema, model); - return z.union([ - z.literal(true), - z.strictObject({ - _all: z.literal(true).optional(), - ...Object.keys(modelDef.fields).reduce( - (acc, field) => { - acc[field] = z.literal(true).optional(); - return acc; - }, - {} as Record, - ), - }), - ]); - } - - // #endregion - - // #region Aggregate - - @cache() - makeAggregateSchema(model: string) { - return this.mergePluginArgsSchema( - z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - skip: this.makeSkipSchema().optional(), - take: this.makeTakeSchema().optional(), - orderBy: this.orArray(this.makeOrderBySchema(model, true, false), true).optional(), - _count: this.makeCountAggregateInputSchema(model).optional(), - _avg: this.makeSumAvgInputSchema(model).optional(), - _sum: this.makeSumAvgInputSchema(model).optional(), - _min: this.makeMinMaxInputSchema(model).optional(), - _max: this.makeMinMaxInputSchema(model).optional(), - }), - 'aggregate', - ).optional(); - } - - @cache() - makeSumAvgInputSchema(model: string) { - const modelDef = requireModel(this.schema, model); - return z.strictObject( - Object.keys(modelDef.fields).reduce( - (acc, field) => { - const fieldDef = requireField(this.schema, model, field); - if (this.isNumericField(fieldDef)) { - acc[field] = z.literal(true).optional(); - } - return acc; - }, - {} as Record, - ), - ); - } - - @cache() - makeMinMaxInputSchema(model: string) { - const modelDef = requireModel(this.schema, model); - return z.strictObject( - Object.keys(modelDef.fields).reduce( - (acc, field) => { - const fieldDef = requireField(this.schema, model, field); - if (!fieldDef.relation && !fieldDef.array) { - acc[field] = z.literal(true).optional(); - } - return acc; - }, - {} as Record, - ), - ); - } - - @cache() - private makeGroupBySchema(model: string) { - const modelDef = requireModel(this.schema, model); - const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); - const bySchema = - nonRelationFields.length > 0 - ? this.orArray(z.enum(nonRelationFields as [string, ...string[]]), true) - : z.never(); - - const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - orderBy: this.orArray(this.makeOrderBySchema(model, false, true), true).optional(), - by: bySchema, - having: this.makeHavingSchema(model).optional(), - skip: this.makeSkipSchema().optional(), - take: this.makeTakeSchema().optional(), - _count: this.makeCountAggregateInputSchema(model).optional(), - _avg: this.makeSumAvgInputSchema(model).optional(), - _sum: this.makeSumAvgInputSchema(model).optional(), - _min: this.makeMinMaxInputSchema(model).optional(), - _max: this.makeMinMaxInputSchema(model).optional(), - }); - - let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'groupBy'); - - // fields used in `having` must be either in the `by` list, or aggregations - schema = schema.refine((value: any) => { - const bys = typeof value.by === 'string' ? [value.by] : value.by; - if (value.having && typeof value.having === 'object') { - for (const [key, val] of Object.entries(value.having)) { - if (AggregateOperators.includes(key as any)) { - continue; - } - if (bys.includes(key)) { - continue; - } - // we have a key not mentioned in `by`, in this case it must only use - // aggregations in the condition - - // 1. payload must be an object - if (!val || typeof val !== 'object') { - return false; - } - // 2. payload must only contain aggregations - if (!this.onlyAggregationFields(val)) { - return false; - } - } - } - return true; - }, 'fields in "having" must be in "by"'); - - // fields used in `orderBy` must be either in the `by` list, or aggregations - schema = schema.refine((value: any) => { - const bys = typeof value.by === 'string' ? [value.by] : value.by; - if ( - value.orderBy && - Object.keys(value.orderBy) - .filter((f) => !AggregateOperators.includes(f as AggregateOperators)) - .some((key) => !bys.includes(key)) - ) { - return false; - } else { - return true; - } - }, 'fields in "orderBy" must be in "by"'); - - return schema; - } - - private onlyAggregationFields(val: object) { - for (const [key, value] of Object.entries(val)) { - if (AggregateOperators.includes(key as any)) { - // aggregation field - continue; - } - if (LOGICAL_COMBINATORS.includes(key as any)) { - // logical operators - if (enumerate(value).every((v) => this.onlyAggregationFields(v))) { - continue; - } - } - return false; - } - return true; - } - - private makeHavingSchema(model: string) { - // `makeWhereSchema` is cached - return this.makeWhereSchema(model, false, true, true); - } - - // #endregion - - // #region Procedures - - @cache() - private makeProcedureParamSchema(param: { type: string; array?: boolean; optional?: boolean }): z.ZodType { - let schema: z.ZodType; - - if (isTypeDef(this.schema, param.type)) { - schema = this.makeTypeDefSchema(param.type); - } else if (isEnum(this.schema, param.type)) { - schema = this.makeEnumSchema(param.type); - } else if (param.type in (this.schema.models ?? {})) { - // For model-typed values, accept any object (no deep shape validation). - schema = z.record(z.string(), z.unknown()); - } else { - // Builtin scalar types. - schema = this.makeScalarSchema(param.type as BuiltinType); - - // If a type isn't recognized by any of the above branches, `makeScalarSchema` returns `unknown`. - // Treat it as configuration/schema error. - if (schema instanceof z.ZodUnknown) { - throw createInternalError(`Unsupported procedure parameter type: ${param.type}`); - } - } - - if (param.array) { - schema = schema.array(); - } - if (param.optional) { - schema = schema.optional(); - } - - return schema; - } - - // #endregion - - // #region Cache Management - - getCache(cacheKey: string) { - return this.schemaCache.get(cacheKey); - } - - setCache(cacheKey: string, schema: ZodType) { - return this.schemaCache.set(cacheKey, schema); - } - - // @ts-ignore - private printCacheStats(detailed = false) { - console.log('Schema cache size:', this.schemaCache.size); - if (detailed) { - for (const key of this.schemaCache.keys()) { - console.log(`\t${key}`); - } - } - } - - // #endregion - - // #region Helpers - - @cache() - private makeSkipSchema() { - return z.number().int().nonnegative(); - } - - @cache() - private makeTakeSchema() { - return z.number().int(); - } - - private refineForSelectIncludeMutuallyExclusive(schema: ZodType) { - return schema.refine( - (value: any) => !(value['select'] && value['include']), - '"select" and "include" cannot be used together', - ); - } - - private refineForSelectOmitMutuallyExclusive(schema: ZodType) { - return schema.refine( - (value: any) => !(value['select'] && value['omit']), - '"select" and "omit" cannot be used together', - ); - } - - private nullableIf(schema: ZodType, nullable: boolean) { - return nullable ? schema.nullable() : schema; - } - - private orArray(schema: T, canBeArray: boolean) { - return canBeArray ? z.union([schema, z.array(schema)]) : schema; - } - - private isNumericField(fieldDef: FieldDef) { - return NUMERIC_FIELD_TYPES.includes(fieldDef.type) && !fieldDef.array; - } - - private get providerSupportsCaseSensitivity() { - return this.schema.provider.type === 'postgresql'; - } - - /** - * Gets the effective set of allowed FilterKind values for a specific model and field. - * Respects the precedence: model[field] > model.$all > $all[field] > $all.$all. - */ - private getEffectiveFilterKinds(model: string | undefined, field: string): string[] | undefined { - if (!model) { - // no restrictions - return undefined; - } - - const slicing = this.options.slicing; - if (!slicing?.models) { - // no slicing or no model-specific slicing, no restrictions - return undefined; - } - - // A string-indexed view of slicing.models that avoids unsafe 'as any' while still - // allowing runtime access by model name. The value shape matches FieldSlicingOptions. - type FieldConfig = { includedFilterKinds?: readonly string[]; excludedFilterKinds?: readonly string[] }; - type FieldsRecord = { $all?: FieldConfig } & Record; - type ModelConfig = { fields?: FieldsRecord }; - const modelsRecord = slicing.models as Record; - - // Check field-level settings for the specific model - const modelConfig = modelsRecord[lowerCaseFirst(model)]; - if (modelConfig?.fields) { - const fieldConfig = modelConfig.fields[field]; - if (fieldConfig) { - return this.computeFilterKinds(fieldConfig.includedFilterKinds, fieldConfig.excludedFilterKinds); - } - - // Fallback to field-level $all for the specific model - const allFieldsConfig = modelConfig.fields['$all']; - if (allFieldsConfig) { - return this.computeFilterKinds( - allFieldsConfig.includedFilterKinds, - allFieldsConfig.excludedFilterKinds, - ); - } - } - - // Fallback to model-level $all - const allModelsConfig = modelsRecord['$all']; - if (allModelsConfig?.fields) { - // Check specific field in $all model config before falling back to $all.$all - const allModelsFieldConfig = allModelsConfig.fields[field]; - if (allModelsFieldConfig) { - return this.computeFilterKinds( - allModelsFieldConfig.includedFilterKinds, - allModelsFieldConfig.excludedFilterKinds, - ); - } - - // Fallback to $all.$all - const allModelsAllFieldsConfig = allModelsConfig.fields['$all']; - if (allModelsAllFieldsConfig) { - return this.computeFilterKinds( - allModelsAllFieldsConfig.includedFilterKinds, - allModelsAllFieldsConfig.excludedFilterKinds, - ); - } - } - - return undefined; // No restrictions - } - - /** - * Computes the effective set of filter kinds based on inclusion and exclusion lists. - */ - private computeFilterKinds(included: readonly string[] | undefined, excluded: readonly string[] | undefined) { - let result: string[] | undefined; - - if (included !== undefined) { - // Start with the included set - result = [...included]; - } - - if (excluded !== undefined) { - if (!result) { - // If no inclusion list, start with all filter kinds - result = [...this.allFilterKinds]; - } - // Remove excluded kinds - for (const kind of excluded) { - result = result.filter((k) => k !== kind); - } - } - - return result; - } - - /** - * Filters operators based on allowed filter kinds. - */ - private trimFilterOperators>( - operators: T, - allowedKinds: string[] | undefined, - ): Partial { - if (!allowedKinds) { - return operators; // No restrictions - } - - return Object.fromEntries( - Object.entries(operators).filter(([key, _]) => { - return ( - !(key in FILTER_PROPERTY_TO_KIND) || - allowedKinds.includes(FILTER_PROPERTY_TO_KIND[key as keyof typeof FILTER_PROPERTY_TO_KIND]) - ); - }), - ) as Partial; - } - - private createUnionFilterSchema( - valueSchema: ZodType, - optional: boolean, - components: Record, - allowedFilterKinds: string[] | undefined, - ) { - // If all filter operators are excluded - if (Object.keys(components).length === 0) { - // if equality filters are allowed, allow direct value - if (!allowedFilterKinds || allowedFilterKinds.includes('Equality')) { - return this.nullableIf(valueSchema, optional); - } - // otherwise nothing is allowed - return z.never(); - } - - if (!allowedFilterKinds || allowedFilterKinds.includes('Equality')) { - // direct value or filter operators - return z.union([this.nullableIf(valueSchema, optional), z.strictObject(components)]); - } else { - // filter operators - return z.strictObject(components); - } - } - - /** - * Checks if a model is included in the slicing configuration. - * Returns true if the model is allowed, false if it's excluded. - */ - private isModelAllowed(targetModel: string): boolean { - const slicing = this.options.slicing; - if (!slicing) { - return true; // No slicing, all models allowed - } - - const { includedModels, excludedModels } = slicing; - - // If includedModels is specified, only those models are allowed - if (includedModels !== undefined) { - if (!includedModels.includes(targetModel as any)) { - return false; - } - } - - // If excludedModels is specified, those models are not allowed - if (excludedModels !== undefined) { - if (excludedModels.includes(targetModel as any)) { - return false; - } - } - - return true; - } - - // #endregion -} +export { InputValidator } from './validator'; diff --git a/packages/orm/src/client/crud/validator/validator.ts b/packages/orm/src/client/crud/validator/validator.ts new file mode 100644 index 000000000..1acba6f15 --- /dev/null +++ b/packages/orm/src/client/crud/validator/validator.ts @@ -0,0 +1,288 @@ +import { invariant } from '@zenstackhq/common-helpers'; +import { match } from 'ts-pattern'; +import { ZodType } from 'zod'; +import { type GetModels, type ProcedureDef, type SchemaDef } from '../../../schema'; +import { formatError } from '../../../utils/zod-utils'; +import type { ClientContract } from '../../contract'; +import { + type AggregateArgs, + type CountArgs, + type CreateArgs, + type CreateManyAndReturnArgs, + type CreateManyArgs, + type DeleteArgs, + type DeleteManyArgs, + type ExistsArgs, + type FindArgs, + type GroupByArgs, + type UpdateArgs, + type UpdateManyAndReturnArgs, + type UpdateManyArgs, + type UpsertArgs, +} from '../../crud-types'; +import { createInvalidInputError } from '../../errors'; +import { ZodSchemaFactory } from '../../zod/factory'; + +type GetSchemaFunc = (model: GetModels) => ZodType; + +export class InputValidator { + readonly zodFactory: ZodSchemaFactory; + + constructor(private readonly client: ClientContract) { + this.zodFactory = new ZodSchemaFactory(client); + } + + // #region Entry points + + validateFindArgs( + model: GetModels, + args: unknown, + operation: 'findFirst' | 'findUnique' | 'findMany', + ): FindArgs, any, true> | undefined { + return this.validate, any, true> | undefined>( + model, + operation, + (model) => + match(operation) + .with('findFirst', () => this.zodFactory.makeFindFirstSchema(model)) + .with('findUnique', () => this.zodFactory.makeFindUniqueSchema(model)) + .with('findMany', () => this.zodFactory.makeFindManySchema(model)) + .exhaustive(), + args, + ); + } + + validateExistsArgs( + model: GetModels, + args: unknown, + ): ExistsArgs, any> | undefined { + return this.validate, any> | undefined>( + model, + 'exists', + (model) => this.zodFactory.makeExistsSchema(model), + args, + ); + } + + validateCreateArgs(model: GetModels, args: unknown): CreateArgs, any> { + return this.validate, any>>( + model, + 'create', + (model) => this.zodFactory.makeCreateSchema(model), + args, + ); + } + + validateCreateManyArgs(model: GetModels, args: unknown): CreateManyArgs> { + return this.validate>>( + model, + 'createMany', + (model) => this.zodFactory.makeCreateManySchema(model), + args, + ); + } + + validateCreateManyAndReturnArgs( + model: GetModels, + args: unknown, + ): CreateManyAndReturnArgs, any> | undefined { + return this.validate, any> | undefined>( + model, + 'createManyAndReturn', + (model) => this.zodFactory.makeCreateManyAndReturnSchema(model), + args, + ); + } + + validateUpdateArgs(model: GetModels, args: unknown): UpdateArgs, any> { + return this.validate, any>>( + model, + 'update', + (model) => this.zodFactory.makeUpdateSchema(model), + args, + ); + } + + validateUpdateManyArgs(model: GetModels, args: unknown): UpdateManyArgs, any> { + return this.validate, any>>( + model, + 'updateMany', + (model) => this.zodFactory.makeUpdateManySchema(model), + args, + ); + } + + validateUpdateManyAndReturnArgs( + model: GetModels, + args: unknown, + ): UpdateManyAndReturnArgs, any> { + return this.validate, any>>( + model, + 'updateManyAndReturn', + (model) => this.zodFactory.makeUpdateManyAndReturnSchema(model), + args, + ); + } + + validateUpsertArgs(model: GetModels, args: unknown): UpsertArgs, any> { + return this.validate, any>>( + model, + 'upsert', + (model) => this.zodFactory.makeUpsertSchema(model), + args, + ); + } + + validateDeleteArgs(model: GetModels, args: unknown): DeleteArgs, any> { + return this.validate, any>>( + model, + 'delete', + (model) => this.zodFactory.makeDeleteSchema(model), + args, + ); + } + + validateDeleteManyArgs( + model: GetModels, + args: unknown, + ): DeleteManyArgs, any> | undefined { + return this.validate, any> | undefined>( + model, + 'deleteMany', + (model) => this.zodFactory.makeDeleteManySchema(model), + args, + ); + } + + validateCountArgs(model: GetModels, args: unknown): CountArgs, any> | undefined { + return this.validate, any> | undefined>( + model, + 'count', + (model) => this.zodFactory.makeCountSchema(model), + args, + ); + } + + validateAggregateArgs(model: GetModels, args: unknown): AggregateArgs, any> { + return this.validate, any>>( + model, + 'aggregate', + (model) => this.zodFactory.makeAggregateSchema(model), + args, + ); + } + + validateGroupByArgs(model: GetModels, args: unknown): GroupByArgs, any> { + return this.validate, any>>( + model, + 'groupBy', + (model) => this.zodFactory.makeGroupBySchema(model), + args, + ); + } + + // TODO: turn it into a Zod schema and cache + validateProcedureInput(proc: string, input: unknown): unknown { + const procDef = (this.client.$schema.procedures ?? {})[proc] as ProcedureDef | undefined; + invariant(procDef, `Procedure "${proc}" not found in schema`); + + const params = Object.values(procDef.params ?? {}); + + // For procedures where every parameter is optional, allow omitting the input entirely. + if (typeof input === 'undefined') { + if (params.length === 0) { + return undefined; + } + if (params.every((p) => p.optional)) { + return undefined; + } + throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); + } + + if (typeof input !== 'object' || input === null || Array.isArray(input)) { + throw createInvalidInputError('Procedure input must be an object', `$procs.${proc}`); + } + + const envelope = input as Record; + const argsPayload = Object.prototype.hasOwnProperty.call(envelope, 'args') ? (envelope as any).args : undefined; + + if (params.length === 0) { + if (typeof argsPayload === 'undefined') { + return input; + } + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); + } + if (Object.keys(argsPayload as any).length === 0) { + return input; + } + throw createInvalidInputError('Procedure does not accept arguments', `$procs.${proc}`); + } + + if (typeof argsPayload === 'undefined') { + if (params.every((p) => p.optional)) { + return input; + } + throw createInvalidInputError('Missing procedure arguments', `$procs.${proc}`); + } + + if (!argsPayload || typeof argsPayload !== 'object' || Array.isArray(argsPayload)) { + throw createInvalidInputError('Procedure `args` must be an object', `$procs.${proc}`); + } + + const obj = argsPayload as Record; + + for (const param of params) { + const value = (obj as any)[param.name]; + + if (!Object.prototype.hasOwnProperty.call(obj, param.name)) { + if (param.optional) { + continue; + } + throw createInvalidInputError(`Missing procedure argument: ${param.name}`, `$procs.${proc}`); + } + + if (typeof value === 'undefined') { + if (param.optional) { + continue; + } + throw createInvalidInputError( + `Invalid procedure argument: ${param.name} is required`, + `$procs.${proc}`, + ); + } + + const schema = this.zodFactory.makeProcedureParamSchema(param); + const parsed = schema.safeParse(value); + if (!parsed.success) { + throw createInvalidInputError( + `Invalid procedure argument: ${param.name}: ${formatError(parsed.error)}`, + `$procs.${proc}`, + ); + } + } + + return input; + } + + // #endregion + + // #region Validation helpers + + private validate(model: GetModels, operation: string, getSchema: GetSchemaFunc, args: unknown) { + const schema = getSchema(model); + const { error, data } = schema.safeParse(args); + if (error) { + throw createInvalidInputError( + `Invalid ${operation} args for model "${model}": ${formatError(error)}`, + model, + { + cause: error, + }, + ); + } + return data as T; + } + + // #endregion +} diff --git a/packages/orm/src/client/index.ts b/packages/orm/src/client/index.ts index 25015c353..41e313faa 100644 --- a/packages/orm/src/client/index.ts +++ b/packages/orm/src/client/index.ts @@ -21,3 +21,4 @@ export type { ZenStackPromise } from './promise'; export type { ToKysely } from './query-builder'; export * as QueryUtils from './query-utils'; export type * from './type-utils'; +export * from './zod'; diff --git a/packages/orm/src/client/crud/validator/cache-decorator.ts b/packages/orm/src/client/zod/cache-decorator.ts similarity index 100% rename from packages/orm/src/client/crud/validator/cache-decorator.ts rename to packages/orm/src/client/zod/cache-decorator.ts diff --git a/packages/orm/src/client/zod/factory.ts b/packages/orm/src/client/zod/factory.ts new file mode 100644 index 000000000..8ad67de6b --- /dev/null +++ b/packages/orm/src/client/zod/factory.ts @@ -0,0 +1,2106 @@ +import { enumerate, invariant, lowerCaseFirst } from '@zenstackhq/common-helpers'; +import { ZodUtils } from '@zenstackhq/zod'; +import Decimal from 'decimal.js'; +import { match, P } from 'ts-pattern'; +import { z, ZodObject, ZodType } from 'zod'; +import { AnyNullClass, DbNullClass, JsonNullClass } from '../../common-types'; +import { + type AttributeApplication, + type BuiltinType, + type FieldDef, + type GetModels, + type SchemaDef, +} from '../../schema'; +import { extractFields } from '../../utils/object-utils'; +import { AggregateOperators, FILTER_PROPERTY_TO_KIND, LOGICAL_COMBINATORS, NUMERIC_FIELD_TYPES } from '../constants'; +import type { ClientContract } from '../contract'; +import type { + AggregateArgs, + CountArgs, + CreateArgs, + CreateManyAndReturnArgs, + CreateManyArgs, + DeleteArgs, + DeleteManyArgs, + ExistsArgs, + FindFirstArgs, + FindManyArgs, + FindUniqueArgs, + GroupByArgs, + UpdateArgs, + UpdateManyAndReturnArgs, + UpdateManyArgs, + UpsertArgs, +} from '../crud-types'; +import { + CoreCreateOperations, + CoreDeleteOperations, + CoreReadOperations, + CoreUpdateOperations, + type CoreCrudOperations, +} from '../crud/operations/base'; +import { createInternalError } from '../errors'; +import type { ClientOptions } from '../options'; +import type { AnyPlugin, ExtQueryArgsBase, RuntimePlugin } from '../plugin'; +import { + fieldHasDefaultValue, + getDiscriminatorField, + getEnum, + getTypeDef, + getUniqueFields, + isEnum, + isTypeDef, + requireField, + requireModel, +} from '../query-utils'; +import { cache } from './cache-decorator'; + +/** + * Minimal field information needed for filter schema generation. + */ +type FieldInfo = { + name: string; + type: string; + optional?: boolean; + array?: boolean; +}; + +/** + * Create a factory for generating Zod schemas to validate ORM query inputs. + */ +export function createQuerySchemaFactory< + Schema extends SchemaDef, + Options extends ClientOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, +>(client: ClientContract): ZodSchemaFactory; + +/** + * Create a factory for generating Zod schemas to validate ORM query inputs. + */ +export function createQuerySchemaFactory< + Schema extends SchemaDef, + Options extends ClientOptions = ClientOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, +>(schema: Schema, options?: Options): ZodSchemaFactory; + +export function createQuerySchemaFactory(clientOrSchema: any, options?: any) { + return new ZodSchemaFactory(clientOrSchema, options); +} + +/** + * Factory class responsible for creating and caching Zod schemas for ORM input validation. + */ +export class ZodSchemaFactory< + Schema extends SchemaDef, + Options extends ClientOptions = ClientOptions, + ExtQueryArgs extends ExtQueryArgsBase = {}, +> { + private readonly schemaCache = new Map(); + private readonly allFilterKinds = [...new Set(Object.values(FILTER_PROPERTY_TO_KIND))]; + private readonly schema: Schema; + private readonly options: Options; + + constructor(client: ClientContract); + constructor(schema: Schema, options?: Options); + constructor(clientOrSchema: any, options?: Options) { + if ('$schema' in clientOrSchema) { + this.schema = clientOrSchema.$schema; + this.options = clientOrSchema.$options; + } else { + this.schema = clientOrSchema; + this.options = options || ({} as Options); + } + } + + private get plugins(): RuntimePlugin[] { + return this.options.plugins ?? []; + } + + private get extraValidationsEnabled() { + return this.options.validateInput !== false; + } + + // #region Cache Management + + // @ts-ignore + private getCache(cacheKey: string) { + return this.schemaCache.get(cacheKey); + } + + // @ts-ignore + private setCache(cacheKey: string, schema: ZodType) { + return this.schemaCache.set(cacheKey, schema); + } + + // @ts-ignore + private printCacheStats(detailed = false) { + console.log('Schema cache size:', this.schemaCache.size); + if (detailed) { + for (const key of this.schemaCache.keys()) { + console.log(`\t${key}`); + } + } + } + + // #endregion + + // #region Find + + makeFindUniqueSchema>( + model: Model, + ): ZodType> { + return this.makeFindSchema(model, 'findUnique') as ZodType< + FindUniqueArgs + >; + } + + makeFindFirstSchema>( + model: Model, + ): ZodType | undefined> { + return this.makeFindSchema(model, 'findFirst') as ZodType< + FindFirstArgs | undefined + >; + } + + makeFindManySchema>( + model: Model, + ): ZodType | undefined> { + return this.makeFindSchema(model, 'findMany') as ZodType< + FindManyArgs | undefined + >; + } + + @cache() + private makeFindSchema(model: string, operation: CoreCrudOperations) { + const fields: Record = {}; + const unique = operation === 'findUnique'; + const findOne = operation === 'findUnique' || operation === 'findFirst'; + const where = this.makeWhereSchema(model, unique); + if (unique) { + fields['where'] = where; + } else { + fields['where'] = where.optional(); + } + + fields['select'] = this.makeSelectSchema(model).optional().nullable(); + fields['include'] = this.makeIncludeSchema(model).optional().nullable(); + fields['omit'] = this.makeOmitSchema(model).optional().nullable(); + + if (!unique) { + fields['skip'] = this.makeSkipSchema().optional(); + if (findOne) { + fields['take'] = z.literal(1).optional(); + } else { + fields['take'] = this.makeTakeSchema().optional(); + } + fields['orderBy'] = this.orArray(this.makeOrderBySchema(model, true, false), true).optional(); + fields['cursor'] = this.makeCursorSchema(model).optional(); + fields['distinct'] = this.makeDistinctSchema(model).optional(); + } + + const baseSchema = z.strictObject(fields); + let result: ZodType = this.mergePluginArgsSchema(baseSchema, operation); + result = this.refineForSelectIncludeMutuallyExclusive(result); + result = this.refineForSelectOmitMutuallyExclusive(result); + + if (!unique) { + result = result.optional(); + } + return result; + } + + @cache() + makeExistsSchema>( + model: Model, + ): ZodType | undefined> { + const baseSchema = z.strictObject({ + where: this.makeWhereSchema(model, false).optional(), + }); + return this.mergePluginArgsSchema(baseSchema, 'exists').optional() as ZodType< + ExistsArgs | undefined + >; + } + + private makeScalarSchema(type: string, attributes?: readonly AttributeApplication[]) { + if (this.schema.typeDefs && type in this.schema.typeDefs) { + return this.makeTypeDefSchema(type); + } else if (this.schema.enums && type in this.schema.enums) { + return this.makeEnumSchema(type); + } else { + return match(type) + .with('String', () => + this.extraValidationsEnabled ? ZodUtils.addStringValidation(z.string(), attributes) : z.string(), + ) + .with('Int', () => + this.extraValidationsEnabled + ? ZodUtils.addNumberValidation(z.number().int(), attributes) + : z.number().int(), + ) + .with('Float', () => + this.extraValidationsEnabled ? ZodUtils.addNumberValidation(z.number(), attributes) : z.number(), + ) + .with('Boolean', () => z.boolean()) + .with('BigInt', () => + z.union([ + this.extraValidationsEnabled + ? ZodUtils.addNumberValidation(z.number().int(), attributes) + : z.number().int(), + this.extraValidationsEnabled + ? ZodUtils.addBigIntValidation(z.bigint(), attributes) + : z.bigint(), + ]), + ) + .with('Decimal', () => { + return z.union([ + this.extraValidationsEnabled + ? ZodUtils.addNumberValidation(z.number(), attributes) + : z.number(), + ZodUtils.addDecimalValidation(z.instanceof(Decimal), attributes, this.extraValidationsEnabled), + ZodUtils.addDecimalValidation(z.string(), attributes, this.extraValidationsEnabled), + ]); + }) + .with('DateTime', () => z.union([z.date(), z.iso.datetime()])) + .with('Bytes', () => z.instanceof(Uint8Array)) + .with('Json', () => this.makeJsonValueSchema(false, false)) + .otherwise(() => z.unknown()); + } + } + + @cache() + private makeEnumSchema(_enum: string) { + const enumDef = getEnum(this.schema, _enum); + invariant(enumDef, `Enum "${_enum}" not found in schema`); + return z.enum(Object.keys(enumDef.values) as [string, ...string[]]); + } + + @cache() + private makeTypeDefSchema(type: string): ZodType { + const typeDef = getTypeDef(this.schema, type); + invariant(typeDef, `Type definition "${type}" not found in schema`); + const schema = z.looseObject( + Object.fromEntries( + Object.entries(typeDef.fields).map(([field, def]) => { + let fieldSchema = this.makeScalarSchema(def.type); + if (def.array) { + fieldSchema = fieldSchema.array(); + } + if (def.optional) { + fieldSchema = fieldSchema.nullish(); + } + return [field, fieldSchema]; + }), + ), + ); + + // zod doesn't preserve object field order after parsing, here we use a + // validation-only custom schema and use the original data if parsing + // is successful + const finalSchema = z.any().superRefine((value, ctx) => { + const parseResult = schema.safeParse(value); + if (!parseResult.success) { + parseResult.error.issues.forEach((issue) => ctx.addIssue(issue as any)); + } + }); + + return finalSchema; + } + + @cache() + makeWhereSchema(model: string, unique: boolean, withoutRelationFields = false, withAggregations = false): ZodType { + const modelDef = requireModel(this.schema, model); + + // unique field used in unique filters bypass filter slicing + const uniqueFieldNames = unique + ? getUniqueFields(this.schema, model) + .filter( + (uf): uf is { name: string; def: FieldDef } => + // single-field unique + 'def' in uf, + ) + .map((uf) => uf.name) + : undefined; + + const fields: Record = {}; + for (const field of Object.keys(modelDef.fields)) { + const fieldDef = requireField(this.schema, model, field); + let fieldSchema: ZodType | undefined; + + if (fieldDef.relation) { + if (withoutRelationFields) { + continue; + } + + // Check if Relation filter kind is allowed + const allowedFilterKinds = this.getEffectiveFilterKinds(model, field); + if (allowedFilterKinds && !allowedFilterKinds.includes('Relation')) { + // Relation filters are not allowed for this field - use z.never() + fieldSchema = z.never(); + } else { + fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional()); + + // optional to-one relation allows null + fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional); + + if (fieldDef.array) { + // to-many relation + fieldSchema = z.union([ + fieldSchema, + z.strictObject({ + some: fieldSchema.optional(), + every: fieldSchema.optional(), + none: fieldSchema.optional(), + }), + ]); + } else { + // to-one relation + fieldSchema = z.union([ + fieldSchema, + z.strictObject({ + is: fieldSchema.optional(), + isNot: fieldSchema.optional(), + }), + ]); + } + } + } else { + const ignoreSlicing = !!uniqueFieldNames?.includes(field); + + const enumDef = getEnum(this.schema, fieldDef.type); + if (enumDef) { + // enum + if (Object.keys(enumDef.values).length > 0) { + fieldSchema = this.makeEnumFilterSchema(model, fieldDef, withAggregations, ignoreSlicing); + } + } else if (fieldDef.array) { + // array field + fieldSchema = this.makeArrayFilterSchema(model, fieldDef); + } else if (this.isTypeDefType(fieldDef.type)) { + fieldSchema = this.makeTypedJsonFilterSchema(model, fieldDef); + } else { + // primitive field + fieldSchema = this.makePrimitiveFilterSchema(model, fieldDef, withAggregations, ignoreSlicing); + } + } + + if (fieldSchema) { + fields[field] = fieldSchema.optional(); + } + } + + if (unique) { + // add compound unique fields, e.g. `{ id1_id2: { id1: 1, id2: 1 } }` + // compound-field filters are not affected by slicing + const uniqueFields = getUniqueFields(this.schema, model); + for (const uniqueField of uniqueFields) { + if ('defs' in uniqueField) { + fields[uniqueField.name] = z + .object( + Object.fromEntries( + Object.entries(uniqueField.defs).map(([key, def]) => { + invariant(!def.relation, 'unique field cannot be a relation'); + let fieldSchema: ZodType; + const enumDef = getEnum(this.schema, def.type); + if (enumDef) { + // enum + if (Object.keys(enumDef.values).length > 0) { + fieldSchema = this.makeEnumFilterSchema(model, def, false, true); + } else { + fieldSchema = z.never(); + } + } else { + fieldSchema = this.makePrimitiveFilterSchema(model, def, false, true); + } + return [key, fieldSchema]; + }), + ), + ) + .optional(); + } + } + } + + // expression builder + fields['$expr'] = z.custom((v) => typeof v === 'function', { error: '"$expr" must be a function' }).optional(); + + // logical operators + fields['AND'] = this.orArray( + z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)), + true, + ).optional(); + fields['OR'] = z + .lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)) + .array() + .optional(); + fields['NOT'] = this.orArray( + z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)), + true, + ).optional(); + + const baseWhere = z.strictObject(fields); + let result: ZodType = baseWhere; + + if (unique) { + // requires at least one unique field (field set) is required + const uniqueFields = getUniqueFields(this.schema, model); + if (uniqueFields.length === 0) { + throw createInternalError(`Model "${model}" has no unique fields`); + } + + if (uniqueFields.length === 1) { + // only one unique field (set), mark the field(s) required + result = baseWhere.required({ + [uniqueFields[0]!.name]: true, + } as any); + } else { + result = baseWhere.refine((value) => { + // check that at least one unique field is set + return uniqueFields.some(({ name }) => value[name] !== undefined); + }, `At least one unique field or field set must be set`); + } + } + + return result; + } + + @cache() + private makeTypedJsonFilterSchema(contextModel: string | undefined, fieldInfo: FieldInfo) { + const field = fieldInfo.name; + const type = fieldInfo.type; + const optional = !!fieldInfo.optional; + const array = !!fieldInfo.array; + + const typeDef = getTypeDef(this.schema, type); + invariant(typeDef, `Type definition "${type}" not found in schema`); + + const candidates: ZodType[] = []; + + if (!array) { + // fields filter + const fieldSchemas: Record = {}; + for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { + if (this.isTypeDefType(fieldDef.type)) { + // recursive typed JSON - use same model/field for nested typed JSON + fieldSchemas[fieldName] = this.makeTypedJsonFilterSchema(contextModel, fieldDef).optional(); + } else { + // enum, array, primitives + const enumDef = getEnum(this.schema, fieldDef.type); + if (enumDef) { + fieldSchemas[fieldName] = this.makeEnumFilterSchema(contextModel, fieldDef, false).optional(); + } else if (fieldDef.array) { + fieldSchemas[fieldName] = this.makeArrayFilterSchema(contextModel, fieldDef).optional(); + } else { + fieldSchemas[fieldName] = this.makePrimitiveFilterSchema( + contextModel, + fieldDef, + false, + ).optional(); + } + } + } + + candidates.push(z.strictObject(fieldSchemas)); + } + + const recursiveSchema = z + .lazy(() => this.makeTypedJsonFilterSchema(contextModel, { name: field, type, optional, array: false })) + .optional(); + if (array) { + // array filter + candidates.push( + z.strictObject({ + some: recursiveSchema, + every: recursiveSchema, + none: recursiveSchema, + }), + ); + } else { + // is / isNot filter + candidates.push( + z.strictObject({ + is: recursiveSchema, + isNot: recursiveSchema, + }), + ); + } + + // plain json filter + candidates.push(this.makeJsonFilterSchema(contextModel, field, optional)); + + if (optional) { + // allow null as well + candidates.push(z.null()); + } + + // either plain json filter or field filters + return z.union(candidates); + } + + private isTypeDefType(type: string) { + return this.schema.typeDefs && type in this.schema.typeDefs; + } + + @cache() + private makeEnumFilterSchema( + model: string | undefined, + fieldInfo: FieldInfo, + withAggregations: boolean, + ignoreSlicing: boolean = false, + ) { + const enumName = fieldInfo.type; + const optional = !!fieldInfo.optional; + const array = !!fieldInfo.array; + + const enumDef = getEnum(this.schema, enumName); + invariant(enumDef, `Enum "${enumName}" not found in schema`); + const baseSchema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); + if (array) { + return this.internalMakeArrayFilterSchema(model, fieldInfo.name, baseSchema); + } + const allowedFilterKinds = ignoreSlicing ? undefined : this.getEffectiveFilterKinds(model, fieldInfo.name); + const components = this.makeCommonPrimitiveFilterComponents( + baseSchema, + optional, + () => z.lazy(() => this.makeEnumFilterSchema(model, fieldInfo, withAggregations)), + ['equals', 'in', 'notIn', 'not'], + withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + + return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); + } + + @cache() + private makeArrayFilterSchema(model: string | undefined, fieldInfo: FieldInfo) { + return this.internalMakeArrayFilterSchema( + model, + fieldInfo.name, + this.makeScalarSchema(fieldInfo.type as BuiltinType), + ); + } + + private internalMakeArrayFilterSchema(contextModel: string | undefined, field: string, elementSchema: ZodType) { + const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field); + const operators = { + equals: elementSchema.array().optional(), + has: elementSchema.optional(), + hasEvery: elementSchema.array().optional(), + hasSome: elementSchema.array().optional(), + isEmpty: z.boolean().optional(), + }; + + // Filter operators based on allowed filter kinds + const filteredOperators = this.trimFilterOperators(operators, allowedFilterKinds); + + return z.strictObject(filteredOperators); + } + + @cache() + private makePrimitiveFilterSchema( + contextModel: string | undefined, + fieldInfo: FieldInfo, + withAggregations: boolean, + ignoreSlicing = false, + ) { + const allowedFilterKinds = ignoreSlicing + ? undefined + : this.getEffectiveFilterKinds(contextModel, fieldInfo.name); + const type = fieldInfo.type as BuiltinType; + const optional = !!fieldInfo.optional; + return match(type) + .with('String', () => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with(P.union('Int', 'Float', 'Decimal', 'BigInt'), (type) => + this.makeNumberFilterSchema( + this.makeScalarSchema(type), + optional, + withAggregations, + allowedFilterKinds, + ), + ) + .with('Boolean', () => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with('DateTime', () => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with('Bytes', () => this.makeBytesFilterSchema(optional, withAggregations, allowedFilterKinds)) + .with('Json', () => this.makeJsonFilterSchema(contextModel, fieldInfo.name, optional)) + .with('Unsupported', () => z.never()) + .exhaustive(); + } + + private makeJsonValueSchema(nullable: boolean, forFilter: boolean): ZodType { + const options: ZodType[] = [z.string(), z.number(), z.boolean(), z.instanceof(JsonNullClass)]; + + if (forFilter) { + options.push(z.instanceof(DbNullClass)); + } else { + if (nullable) { + // for mutation, allow DbNull only if nullable + options.push(z.instanceof(DbNullClass)); + } + } + + if (forFilter) { + options.push(z.instanceof(AnyNullClass)); + } + + const schema = z.union([ + ...options, + z.lazy(() => z.union([this.makeJsonValueSchema(false, false), z.null()]).array()), + z.record( + z.string(), + z.lazy(() => z.union([this.makeJsonValueSchema(false, false), z.null()])), + ), + ]); + return this.nullableIf(schema, nullable); + } + + @cache() + private makeJsonFilterSchema(contextModel: string | undefined, field: string, optional: boolean) { + const allowedFilterKinds = this.getEffectiveFilterKinds(contextModel, field); + + // Check if Json filter kind is allowed + if (allowedFilterKinds && !allowedFilterKinds.includes('Json')) { + // Return a never schema if Json filters are not allowed + return z.never(); + } + + const valueSchema = this.makeJsonValueSchema(optional, true); + return z.strictObject({ + path: z.string().optional(), + equals: valueSchema.optional(), + not: valueSchema.optional(), + string_contains: z.string().optional(), + string_starts_with: z.string().optional(), + string_ends_with: z.string().optional(), + mode: this.makeStringModeSchema().optional(), + array_contains: valueSchema.optional(), + array_starts_with: valueSchema.optional(), + array_ends_with: valueSchema.optional(), + }); + } + + @cache() + private makeDateTimeFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { + return this.makeCommonPrimitiveFilterSchema( + z.union([z.iso.datetime(), z.date()]), + optional, + () => z.lazy(() => this.makeDateTimeFilterSchema(optional, withAggregations, allowedFilterKinds)), + withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + } + + @cache() + private makeBooleanFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { + const components = this.makeCommonPrimitiveFilterComponents( + z.boolean(), + optional, + () => z.lazy(() => this.makeBooleanFilterSchema(optional, withAggregations, allowedFilterKinds)), + ['equals', 'not'], + withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + + return this.createUnionFilterSchema(z.boolean(), optional, components, allowedFilterKinds); + } + + @cache() + private makeBytesFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { + const baseSchema = z.instanceof(Uint8Array); + const components = this.makeCommonPrimitiveFilterComponents( + baseSchema, + optional, + () => z.instanceof(Uint8Array), + ['equals', 'in', 'notIn', 'not'], + withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + + return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); + } + + private makeCommonPrimitiveFilterComponents( + baseSchema: ZodType, + optional: boolean, + makeThis: () => ZodType, + supportedOperators: string[] | undefined = undefined, + withAggregations: Array<'_count' | '_avg' | '_sum' | '_min' | '_max'> | undefined = undefined, + allowedFilterKinds: string[] | undefined = undefined, + ) { + const commonAggSchema = () => + this.makeCommonPrimitiveFilterSchema(baseSchema, false, makeThis, undefined, allowedFilterKinds).optional(); + let result = { + equals: this.nullableIf(baseSchema.optional(), optional), + in: baseSchema.array().optional(), + notIn: baseSchema.array().optional(), + lt: baseSchema.optional(), + lte: baseSchema.optional(), + gt: baseSchema.optional(), + gte: baseSchema.optional(), + between: baseSchema.array().length(2).optional(), + not: makeThis().optional(), + ...(withAggregations?.includes('_count') + ? { _count: this.makeNumberFilterSchema(z.number().int(), false, false, undefined).optional() } + : {}), + ...(withAggregations?.includes('_avg') ? { _avg: commonAggSchema() } : {}), + ...(withAggregations?.includes('_sum') ? { _sum: commonAggSchema() } : {}), + ...(withAggregations?.includes('_min') ? { _min: commonAggSchema() } : {}), + ...(withAggregations?.includes('_max') ? { _max: commonAggSchema() } : {}), + }; + if (supportedOperators) { + const keys = [...supportedOperators, ...(withAggregations ?? [])]; + result = extractFields(result, keys) as typeof result; + } + + // Filter operators based on allowed filter kinds + result = this.trimFilterOperators(result, allowedFilterKinds) as typeof result; + + return result; + } + + private makeCommonPrimitiveFilterSchema( + baseSchema: ZodType, + optional: boolean, + makeThis: () => ZodType, + withAggregations: Array | undefined = undefined, + allowedFilterKinds: string[] | undefined = undefined, + ): ZodType { + const components = this.makeCommonPrimitiveFilterComponents( + baseSchema, + optional, + makeThis, + undefined, + withAggregations, + allowedFilterKinds, + ); + + return this.createUnionFilterSchema(baseSchema, optional, components, allowedFilterKinds); + } + + private makeNumberFilterSchema( + baseSchema: ZodType, + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { + return this.makeCommonPrimitiveFilterSchema( + baseSchema, + optional, + () => z.lazy(() => this.makeNumberFilterSchema(baseSchema, optional, withAggregations, allowedFilterKinds)), + withAggregations ? ['_count', '_avg', '_sum', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + } + + private makeStringFilterSchema( + optional: boolean, + withAggregations: boolean, + allowedFilterKinds: string[] | undefined, + ): ZodType { + const baseComponents = this.makeCommonPrimitiveFilterComponents( + z.string(), + optional, + () => z.lazy(() => this.makeStringFilterSchema(optional, withAggregations, allowedFilterKinds)), + undefined, + withAggregations ? ['_count', '_min', '_max'] : undefined, + allowedFilterKinds, + ); + + const stringSpecificOperators = { + startsWith: z.string().optional(), + endsWith: z.string().optional(), + contains: z.string().optional(), + ...(this.providerSupportsCaseSensitivity + ? { + mode: this.makeStringModeSchema().optional(), + } + : {}), + }; + + // Filter string-specific operators based on allowed filter kinds + const filteredStringOperators = this.trimFilterOperators(stringSpecificOperators, allowedFilterKinds); + + const allComponents = { + ...baseComponents, + ...filteredStringOperators, + }; + + return this.createUnionFilterSchema(z.string(), optional, allComponents, allowedFilterKinds); + } + + private makeStringModeSchema() { + return z.union([z.literal('default'), z.literal('insensitive')]); + } + + @cache() + private makeSelectSchema(model: string) { + const modelDef = requireModel(this.schema, model); + const fields: Record = {}; + for (const field of Object.keys(modelDef.fields)) { + const fieldDef = requireField(this.schema, model, field); + if (fieldDef.relation) { + // Check if the target model is allowed by slicing configuration + if (this.isModelAllowed(fieldDef.type)) { + fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + } + } else { + fields[field] = z.boolean().optional(); + } + } + + const _countSchema = this.makeCountSelectionSchema(model); + if (!(_countSchema instanceof z.ZodNever)) { + fields['_count'] = _countSchema; + } + + return z.strictObject(fields); + } + + @cache() + private makeCountSelectionSchema(model: string) { + const modelDef = requireModel(this.schema, model); + const toManyRelations = Object.values(modelDef.fields).filter((def) => def.relation && def.array); + if (toManyRelations.length > 0) { + return z + .union([ + z.literal(true), + z.strictObject({ + select: z.strictObject( + toManyRelations.reduce( + (acc, fieldDef) => ({ + ...acc, + [fieldDef.name]: z + .union([ + z.boolean(), + z.strictObject({ + where: this.makeWhereSchema(fieldDef.type, false, false), + }), + ]) + .optional(), + }), + {} as Record, + ), + ), + }), + ]) + .optional(); + } else { + return z.never(); + } + } + + @cache() + private makeRelationSelectIncludeSchema(model: string, field: string) { + const fieldDef = requireField(this.schema, model, field); + let objSchema: ZodType = z.strictObject({ + ...(fieldDef.array || fieldDef.optional + ? { + // to-many relations and optional to-one relations are filterable + where: z.lazy(() => this.makeWhereSchema(fieldDef.type, false)).optional(), + } + : {}), + select: z + .lazy(() => this.makeSelectSchema(fieldDef.type)) + .optional() + .nullable(), + include: z + .lazy(() => this.makeIncludeSchema(fieldDef.type)) + .optional() + .nullable(), + omit: z + .lazy(() => this.makeOmitSchema(fieldDef.type)) + .optional() + .nullable(), + ...(fieldDef.array + ? { + // to-many relations can be ordered, skipped, taken, and cursor-located + orderBy: z + .lazy(() => this.orArray(this.makeOrderBySchema(fieldDef.type, true, false), true)) + .optional(), + skip: this.makeSkipSchema().optional(), + take: this.makeTakeSchema().optional(), + cursor: this.makeCursorSchema(fieldDef.type).optional(), + distinct: this.makeDistinctSchema(fieldDef.type).optional(), + } + : {}), + }); + + objSchema = this.refineForSelectIncludeMutuallyExclusive(objSchema); + objSchema = this.refineForSelectOmitMutuallyExclusive(objSchema); + + return z.union([z.boolean(), objSchema]); + } + + @cache() + private makeOmitSchema(model: string) { + const modelDef = requireModel(this.schema, model); + const fields: Record = {}; + for (const field of Object.keys(modelDef.fields)) { + const fieldDef = requireField(this.schema, model, field); + if (!fieldDef.relation) { + if (this.options.allowQueryTimeOmitOverride !== false) { + // if override is allowed, use boolean + fields[field] = z.boolean().optional(); + } else { + // otherwise only allow true + fields[field] = z.literal(true).optional(); + } + } + } + return z.strictObject(fields); + } + + @cache() + private makeIncludeSchema(model: string) { + const modelDef = requireModel(this.schema, model); + const fields: Record = {}; + for (const field of Object.keys(modelDef.fields)) { + const fieldDef = requireField(this.schema, model, field); + if (fieldDef.relation) { + // Check if the target model is allowed by slicing configuration + if (this.isModelAllowed(fieldDef.type)) { + fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + } + } + } + + const _countSchema = this.makeCountSelectionSchema(model); + if (!(_countSchema instanceof z.ZodNever)) { + fields['_count'] = _countSchema; + } + + return z.strictObject(fields); + } + + @cache() + private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) { + const modelDef = requireModel(this.schema, model); + const fields: Record = {}; + const sort = z.union([z.literal('asc'), z.literal('desc')]); + for (const field of Object.keys(modelDef.fields)) { + const fieldDef = requireField(this.schema, model, field); + if (fieldDef.relation) { + // relations + if (withRelation) { + fields[field] = z.lazy(() => { + let relationOrderBy = this.makeOrderBySchema(fieldDef.type, withRelation, WithAggregation); + if (fieldDef.array) { + relationOrderBy = relationOrderBy.extend({ + _count: sort, + }); + } + return relationOrderBy.optional(); + }); + } + } else { + // scalars + if (fieldDef.optional) { + fields[field] = z + .union([ + sort, + z.strictObject({ + sort, + nulls: z.union([z.literal('first'), z.literal('last')]), + }), + ]) + .optional(); + } else { + fields[field] = sort.optional(); + } + } + } + + // aggregations + if (WithAggregation) { + const aggregationFields = ['_count', '_avg', '_sum', '_min', '_max']; + for (const agg of aggregationFields) { + fields[agg] = z.lazy(() => this.makeOrderBySchema(model, true, false).optional()); + } + } + + return z.strictObject(fields); + } + + @cache() + private makeDistinctSchema(model: string) { + const modelDef = requireModel(this.schema, model); + const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); + return nonRelationFields.length > 0 ? this.orArray(z.enum(nonRelationFields as any), true) : z.never(); + } + + private makeCursorSchema(model: string) { + // `makeWhereSchema` is already cached + return this.makeWhereSchema(model, true, true).optional(); + } + + // #endregion + + // #region Create + + @cache() + makeCreateSchema>( + model: Model, + ): ZodType> { + const dataSchema = this.makeCreateDataSchema(model, false); + const baseSchema = z.strictObject({ + data: dataSchema, + select: this.makeSelectSchema(model).optional().nullable(), + include: this.makeIncludeSchema(model).optional().nullable(), + omit: this.makeOmitSchema(model).optional().nullable(), + }); + let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'create'); + schema = this.refineForSelectIncludeMutuallyExclusive(schema); + schema = this.refineForSelectOmitMutuallyExclusive(schema); + return schema as ZodType>; + } + + @cache() + makeCreateManySchema>( + model: Model, + ): ZodType> { + return this.mergePluginArgsSchema( + this.makeCreateManyPayloadSchema(model, []), + 'createMany', + ) as unknown as ZodType>; + } + + @cache() + makeCreateManyAndReturnSchema>( + model: Model, + ): ZodType> { + const base = this.makeCreateManyPayloadSchema(model, []); + let result: ZodObject = base.extend({ + select: this.makeSelectSchema(model).optional().nullable(), + omit: this.makeOmitSchema(model).optional().nullable(), + }); + result = this.mergePluginArgsSchema(result, 'createManyAndReturn'); + return this.refineForSelectOmitMutuallyExclusive(result).optional() as ZodType< + CreateManyAndReturnArgs + >; + } + + @cache() + private makeCreateDataSchema( + model: string, + canBeArray: boolean, + withoutFields: string[] = [], + withoutRelationFields = false, + ) { + const uncheckedVariantFields: Record = {}; + const checkedVariantFields: Record = {}; + const modelDef = requireModel(this.schema, model); + const hasRelation = + !withoutRelationFields && + Object.entries(modelDef.fields).some(([f, def]) => !withoutFields.includes(f) && def.relation); + + Object.keys(modelDef.fields).forEach((field) => { + if (withoutFields.includes(field)) { + return; + } + const fieldDef = requireField(this.schema, model, field); + if (fieldDef.computed) { + return; + } + + if (this.isDelegateDiscriminator(fieldDef)) { + // discriminator field is auto-assigned + return; + } + + if (fieldDef.relation) { + if (withoutRelationFields) { + return; + } + // Check if the target model is allowed by slicing configuration + if (!this.isModelAllowed(fieldDef.type)) { + return; + } + const excludeFields: string[] = []; + const oppositeField = fieldDef.relation.opposite; + if (oppositeField) { + excludeFields.push(oppositeField); + const oppositeFieldDef = requireField(this.schema, fieldDef.type, oppositeField); + if (oppositeFieldDef.relation?.fields) { + excludeFields.push(...oppositeFieldDef.relation.fields); + } + } + + let fieldSchema: ZodType = z.lazy(() => + this.makeRelationManipulationSchema(model, field, excludeFields, 'create'), + ); + + if (fieldDef.optional || fieldDef.array) { + // optional or array relations are optional + fieldSchema = fieldSchema.optional(); + } else { + // if all fk fields are optional, the relation is optional + let allFksOptional = false; + if (fieldDef.relation.fields) { + allFksOptional = fieldDef.relation.fields.every((f) => { + const fkDef = requireField(this.schema, model, f); + return fkDef.optional || fieldHasDefaultValue(fkDef); + }); + } + if (allFksOptional) { + fieldSchema = fieldSchema.optional(); + } + } + + // optional to-one relation can be null + if (fieldDef.optional && !fieldDef.array) { + fieldSchema = fieldSchema.nullable(); + } + checkedVariantFields[field] = fieldSchema; + if (fieldDef.array || !fieldDef.relation.references) { + // non-owned relation + uncheckedVariantFields[field] = fieldSchema; + } + } else { + let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes); + + if (fieldDef.array) { + fieldSchema = ZodUtils.addListValidation(fieldSchema.array(), fieldDef.attributes); + fieldSchema = z + .union([ + fieldSchema, + z.strictObject({ + set: fieldSchema, + }), + ]) + .optional(); + } + + if (fieldDef.optional || fieldHasDefaultValue(fieldDef)) { + fieldSchema = fieldSchema.optional(); + } + + if (fieldDef.optional) { + if (fieldDef.type === 'Json') { + // DbNull for Json fields + fieldSchema = z.union([fieldSchema, z.instanceof(DbNullClass)]); + } else { + fieldSchema = fieldSchema.nullable(); + } + } + + uncheckedVariantFields[field] = fieldSchema; + if (!fieldDef.foreignKeyFor) { + // non-fk field + checkedVariantFields[field] = fieldSchema; + } + } + }); + + const uncheckedCreateSchema = this.extraValidationsEnabled + ? ZodUtils.addCustomValidation(z.strictObject(uncheckedVariantFields), modelDef.attributes) + : z.strictObject(uncheckedVariantFields); + const checkedCreateSchema = this.extraValidationsEnabled + ? ZodUtils.addCustomValidation(z.strictObject(checkedVariantFields), modelDef.attributes) + : z.strictObject(checkedVariantFields); + + if (!hasRelation) { + return this.orArray(uncheckedCreateSchema, canBeArray); + } else { + return z.union([ + uncheckedCreateSchema, + checkedCreateSchema, + ...(canBeArray ? [z.array(uncheckedCreateSchema)] : []), + ...(canBeArray ? [z.array(checkedCreateSchema)] : []), + ]); + } + } + + private isDelegateDiscriminator(fieldDef: FieldDef) { + if (!fieldDef.originModel) { + // not inherited from a delegate + return false; + } + const discriminatorField = getDiscriminatorField(this.schema, fieldDef.originModel); + return discriminatorField === fieldDef.name; + } + + @cache() + private makeRelationManipulationSchema( + model: string, + field: string, + withoutFields: string[], + mode: 'create' | 'update', + ) { + const fieldDef = requireField(this.schema, model, field); + const fieldType = fieldDef.type; + const array = !!fieldDef.array; + const fields: Record = { + create: this.makeCreateDataSchema(fieldDef.type, !!fieldDef.array, withoutFields).optional(), + + connect: this.makeConnectDataSchema(fieldType, array).optional(), + + connectOrCreate: this.makeConnectOrCreateDataSchema(fieldType, array, withoutFields).optional(), + }; + + if (array) { + fields['createMany'] = this.makeCreateManyPayloadSchema(fieldType, withoutFields).optional(); + } + + if (mode === 'update') { + if (fieldDef.optional || fieldDef.array) { + // disconnect and delete are only available for optional/to-many relations + fields['disconnect'] = this.makeDisconnectDataSchema(fieldType, array).optional(); + + fields['delete'] = this.makeDeleteRelationDataSchema(fieldType, array, true).optional(); + } + + fields['update'] = array + ? this.orArray( + z.strictObject({ + where: this.makeWhereSchema(fieldType, true), + data: this.makeUpdateDataSchema(fieldType, withoutFields), + }), + true, + ).optional() + : z + .union([ + z.strictObject({ + where: this.makeWhereSchema(fieldType, false).optional(), + data: this.makeUpdateDataSchema(fieldType, withoutFields), + }), + this.makeUpdateDataSchema(fieldType, withoutFields), + ]) + .optional(); + + let upsertWhere = this.makeWhereSchema(fieldType, true); + if (!fieldDef.array) { + // to-one relation, can upsert without where clause + upsertWhere = upsertWhere.optional(); + } + fields['upsert'] = this.orArray( + z.strictObject({ + where: upsertWhere, + create: this.makeCreateDataSchema(fieldType, false, withoutFields), + update: this.makeUpdateDataSchema(fieldType, withoutFields), + }), + true, + ).optional(); + + if (array) { + // to-many relation specifics + fields['set'] = this.makeSetDataSchema(fieldType, true).optional(); + + fields['updateMany'] = this.orArray( + z.strictObject({ + where: this.makeWhereSchema(fieldType, false, true), + data: this.makeUpdateDataSchema(fieldType, withoutFields), + }), + true, + ).optional(); + + fields['deleteMany'] = this.makeDeleteRelationDataSchema(fieldType, true, false).optional(); + } + } + + return z.strictObject(fields); + } + + @cache() + private makeSetDataSchema(model: string, canBeArray: boolean) { + return this.orArray(this.makeWhereSchema(model, true), canBeArray); + } + + @cache() + private makeConnectDataSchema(model: string, canBeArray: boolean) { + return this.orArray(this.makeWhereSchema(model, true), canBeArray); + } + + @cache() + private makeDisconnectDataSchema(model: string, canBeArray: boolean) { + if (canBeArray) { + // to-many relation, must be unique filters + return this.orArray(this.makeWhereSchema(model, true), canBeArray); + } else { + // to-one relation, can be boolean or a regular filter - the entity + // being disconnected is already uniquely identified by its parent + return z.union([z.boolean(), this.makeWhereSchema(model, false)]); + } + } + + @cache() + private makeDeleteRelationDataSchema(model: string, toManyRelation: boolean, uniqueFilter: boolean) { + return toManyRelation + ? this.orArray(this.makeWhereSchema(model, uniqueFilter), true) + : z.union([z.boolean(), this.makeWhereSchema(model, uniqueFilter)]); + } + + @cache() + private makeConnectOrCreateDataSchema(model: string, canBeArray: boolean, withoutFields: string[]) { + const whereSchema = this.makeWhereSchema(model, true); + const createSchema = this.makeCreateDataSchema(model, false, withoutFields); + return this.orArray( + z.strictObject({ + where: whereSchema, + create: createSchema, + }), + canBeArray, + ); + } + + @cache() + private makeCreateManyPayloadSchema(model: string, withoutFields: string[]) { + return z.strictObject({ + data: this.makeCreateDataSchema(model, true, withoutFields, true), + skipDuplicates: z.boolean().optional(), + }); + } + + // #endregion + + // #region Update + + @cache() + makeUpdateSchema>( + model: Model, + ): ZodType> { + const baseSchema = z.strictObject({ + where: this.makeWhereSchema(model, true), + data: this.makeUpdateDataSchema(model), + select: this.makeSelectSchema(model).optional().nullable(), + include: this.makeIncludeSchema(model).optional().nullable(), + omit: this.makeOmitSchema(model).optional().nullable(), + }); + let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'update'); + schema = this.refineForSelectIncludeMutuallyExclusive(schema); + schema = this.refineForSelectOmitMutuallyExclusive(schema); + return schema as ZodType>; + } + + @cache() + makeUpdateManySchema>( + model: Model, + ): ZodType> { + return this.mergePluginArgsSchema( + z.strictObject({ + where: this.makeWhereSchema(model, false).optional(), + data: this.makeUpdateDataSchema(model, [], true), + limit: z.number().int().nonnegative().optional(), + }), + 'updateMany', + ) as unknown as ZodType>; + } + + @cache() + makeUpdateManyAndReturnSchema>( + model: Model, + ): ZodType> { + // plugin extended args schema is merged in `makeUpdateManySchema` + const baseSchema = this.makeUpdateManySchema(model) as unknown as ZodObject; + let schema: ZodType = baseSchema.extend({ + select: this.makeSelectSchema(model).optional().nullable(), + omit: this.makeOmitSchema(model).optional().nullable(), + }); + schema = this.refineForSelectOmitMutuallyExclusive(schema); + return schema as ZodType>; + } + + @cache() + makeUpsertSchema>( + model: Model, + ): ZodType> { + const baseSchema = z.strictObject({ + where: this.makeWhereSchema(model, true), + create: this.makeCreateDataSchema(model, false), + update: this.makeUpdateDataSchema(model), + select: this.makeSelectSchema(model).optional().nullable(), + include: this.makeIncludeSchema(model).optional().nullable(), + omit: this.makeOmitSchema(model).optional().nullable(), + }); + let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'upsert'); + schema = this.refineForSelectIncludeMutuallyExclusive(schema); + schema = this.refineForSelectOmitMutuallyExclusive(schema); + return schema as ZodType>; + } + + @cache() + private makeUpdateDataSchema(model: string, withoutFields: string[] = [], withoutRelationFields = false) { + const uncheckedVariantFields: Record = {}; + const checkedVariantFields: Record = {}; + const modelDef = requireModel(this.schema, model); + const hasRelation = Object.entries(modelDef.fields).some( + ([key, value]) => value.relation && !withoutFields.includes(key), + ); + + Object.keys(modelDef.fields).forEach((field) => { + if (withoutFields.includes(field)) { + return; + } + const fieldDef = requireField(this.schema, model, field); + + if (fieldDef.relation) { + if (withoutRelationFields) { + return; + } + // Check if the target model is allowed by slicing configuration + if (!this.isModelAllowed(fieldDef.type)) { + return; + } + const excludeFields: string[] = []; + const oppositeField = fieldDef.relation.opposite; + if (oppositeField) { + excludeFields.push(oppositeField); + const oppositeFieldDef = requireField(this.schema, fieldDef.type, oppositeField); + if (oppositeFieldDef.relation?.fields) { + excludeFields.push(...oppositeFieldDef.relation.fields); + } + } + let fieldSchema: ZodType = z + .lazy(() => this.makeRelationManipulationSchema(model, field, excludeFields, 'update')) + .optional(); + // optional to-one relation can be null + if (fieldDef.optional && !fieldDef.array) { + fieldSchema = fieldSchema.nullable(); + } + checkedVariantFields[field] = fieldSchema; + if (fieldDef.array || !fieldDef.relation.references) { + // non-owned relation + uncheckedVariantFields[field] = fieldSchema; + } + } else { + let fieldSchema = this.makeScalarSchema(fieldDef.type, fieldDef.attributes); + + if (this.isNumericField(fieldDef)) { + fieldSchema = z.union([ + fieldSchema, + z + .object({ + // TODO: use Decimal/BigInt for incremental updates + set: this.nullableIf(z.number().optional(), !!fieldDef.optional).optional(), + increment: z.number().optional(), + decrement: z.number().optional(), + multiply: z.number().optional(), + divide: z.number().optional(), + }) + .refine( + (v) => Object.keys(v).length === 1, + 'Only one of "set", "increment", "decrement", "multiply", or "divide" can be provided', + ), + ]); + } + + if (fieldDef.array) { + const arraySchema = ZodUtils.addListValidation(fieldSchema.array(), fieldDef.attributes); + fieldSchema = z.union([ + arraySchema, + z + .object({ + set: arraySchema.optional(), + push: z.union([fieldSchema, fieldSchema.array()]).optional(), + }) + .refine((v) => Object.keys(v).length === 1, 'Only one of "set", "push" can be provided'), + ]); + } + + if (fieldDef.optional) { + if (fieldDef.type === 'Json') { + // DbNull for Json fields + fieldSchema = z.union([fieldSchema, z.instanceof(DbNullClass)]); + } else { + fieldSchema = fieldSchema.nullable(); + } + } + + // all fields are optional in update + fieldSchema = fieldSchema.optional(); + + uncheckedVariantFields[field] = fieldSchema; + if (!fieldDef.foreignKeyFor) { + // non-fk field + checkedVariantFields[field] = fieldSchema; + } + } + }); + + const uncheckedUpdateSchema = this.extraValidationsEnabled + ? ZodUtils.addCustomValidation(z.strictObject(uncheckedVariantFields), modelDef.attributes) + : z.strictObject(uncheckedVariantFields); + const checkedUpdateSchema = this.extraValidationsEnabled + ? ZodUtils.addCustomValidation(z.strictObject(checkedVariantFields), modelDef.attributes) + : z.strictObject(checkedVariantFields); + if (!hasRelation) { + return uncheckedUpdateSchema; + } else { + return z.union([uncheckedUpdateSchema, checkedUpdateSchema]); + } + } + + // #endregion + + // #region Delete + + @cache() + makeDeleteSchema>( + model: Model, + ): ZodType> { + const baseSchema = z.strictObject({ + where: this.makeWhereSchema(model, true), + select: this.makeSelectSchema(model).optional().nullable(), + include: this.makeIncludeSchema(model).optional().nullable(), + omit: this.makeOmitSchema(model).optional().nullable(), + }); + let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'delete'); + schema = this.refineForSelectIncludeMutuallyExclusive(schema); + schema = this.refineForSelectOmitMutuallyExclusive(schema); + return schema as ZodType>; + } + + @cache() + makeDeleteManySchema>( + model: Model, + ): ZodType | undefined> { + return this.mergePluginArgsSchema( + z.strictObject({ + where: this.makeWhereSchema(model, false).optional(), + limit: z.number().int().nonnegative().optional(), + }), + 'deleteMany', + ).optional() as unknown as ZodType | undefined>; + } + + // #endregion + + // #region Count + + @cache() + makeCountSchema>( + model: Model, + ): ZodType | undefined> { + return this.mergePluginArgsSchema( + z.strictObject({ + where: this.makeWhereSchema(model, false).optional(), + skip: this.makeSkipSchema().optional(), + take: this.makeTakeSchema().optional(), + orderBy: this.orArray(this.makeOrderBySchema(model, true, false), true).optional(), + select: this.makeCountAggregateInputSchema(model).optional(), + }), + 'count', + ).optional() as ZodType | undefined>; + } + + @cache() + private makeCountAggregateInputSchema(model: string) { + const modelDef = requireModel(this.schema, model); + return z.union([ + z.literal(true), + z.strictObject({ + _all: z.literal(true).optional(), + ...Object.keys(modelDef.fields).reduce( + (acc, field) => { + acc[field] = z.literal(true).optional(); + return acc; + }, + {} as Record, + ), + }), + ]); + } + + // #endregion + + // #region Aggregate + + @cache() + makeAggregateSchema>( + model: Model, + ): ZodType | undefined> { + return this.mergePluginArgsSchema( + z.strictObject({ + where: this.makeWhereSchema(model, false).optional(), + skip: this.makeSkipSchema().optional(), + take: this.makeTakeSchema().optional(), + orderBy: this.orArray(this.makeOrderBySchema(model, true, false), true).optional(), + _count: this.makeCountAggregateInputSchema(model).optional(), + _avg: this.makeSumAvgInputSchema(model).optional(), + _sum: this.makeSumAvgInputSchema(model).optional(), + _min: this.makeMinMaxInputSchema(model).optional(), + _max: this.makeMinMaxInputSchema(model).optional(), + }), + 'aggregate', + ).optional() as ZodType | undefined>; + } + + @cache() + private makeSumAvgInputSchema(model: string) { + const modelDef = requireModel(this.schema, model); + return z.strictObject( + Object.keys(modelDef.fields).reduce( + (acc, field) => { + const fieldDef = requireField(this.schema, model, field); + if (this.isNumericField(fieldDef)) { + acc[field] = z.literal(true).optional(); + } + return acc; + }, + {} as Record, + ), + ); + } + + @cache() + private makeMinMaxInputSchema(model: string) { + const modelDef = requireModel(this.schema, model); + return z.strictObject( + Object.keys(modelDef.fields).reduce( + (acc, field) => { + const fieldDef = requireField(this.schema, model, field); + if (!fieldDef.relation && !fieldDef.array) { + acc[field] = z.literal(true).optional(); + } + return acc; + }, + {} as Record, + ), + ); + } + + // #endregion + + // #region Group By + + @cache() + makeGroupBySchema>( + model: Model, + ): ZodType> { + const modelDef = requireModel(this.schema, model); + const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); + const bySchema = + nonRelationFields.length > 0 + ? this.orArray(z.enum(nonRelationFields as [string, ...string[]]), true) + : z.never(); + + const baseSchema = z.strictObject({ + where: this.makeWhereSchema(model, false).optional(), + orderBy: this.orArray(this.makeOrderBySchema(model, false, true), true).optional(), + by: bySchema, + having: this.makeHavingSchema(model).optional(), + skip: this.makeSkipSchema().optional(), + take: this.makeTakeSchema().optional(), + _count: this.makeCountAggregateInputSchema(model).optional(), + _avg: this.makeSumAvgInputSchema(model).optional(), + _sum: this.makeSumAvgInputSchema(model).optional(), + _min: this.makeMinMaxInputSchema(model).optional(), + _max: this.makeMinMaxInputSchema(model).optional(), + }); + + let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'groupBy'); + + // fields used in `having` must be either in the `by` list, or aggregations + schema = schema.refine((value: any) => { + const bys = enumerate(value.by); + if (value.having && typeof value.having === 'object') { + for (const [key, val] of Object.entries(value.having)) { + if (AggregateOperators.includes(key as any)) { + continue; + } + if (bys.includes(key)) { + continue; + } + // we have a key not mentioned in `by`, in this case it must only use + // aggregations in the condition + + // 1. payload must be an object + if (!val || typeof val !== 'object') { + return false; + } + // 2. payload must only contain aggregations + if (!this.onlyAggregationFields(val)) { + return false; + } + } + } + return true; + }, 'fields in "having" must be in "by"'); + + // fields used in `orderBy` must be either in the `by` list, or aggregations + schema = schema.refine((value: any) => { + const bys = enumerate(value.by); + for (const orderBy of enumerate(value.orderBy)) { + if ( + orderBy && + Object.keys(orderBy) + .filter((f) => !AggregateOperators.includes(f as AggregateOperators)) + .some((key) => !bys.includes(key)) + ) { + return false; + } + } + return true; + }, 'fields in "orderBy" must be in "by"'); + + return schema as ZodType>; + } + + private onlyAggregationFields(val: object) { + for (const [key, value] of Object.entries(val)) { + if (AggregateOperators.includes(key as any)) { + // aggregation field + continue; + } + if (LOGICAL_COMBINATORS.includes(key as any)) { + // logical operators + if (enumerate(value).every((v) => this.onlyAggregationFields(v))) { + continue; + } + } + return false; + } + return true; + } + + private makeHavingSchema(model: string) { + // `makeWhereSchema` is cached + return this.makeWhereSchema(model, false, true, true); + } + + // #endregion + + // #region Procedures + + @cache() + makeProcedureParamSchema(param: { type: string; array?: boolean; optional?: boolean }): ZodType { + let schema: ZodType; + + if (isTypeDef(this.schema, param.type)) { + schema = this.makeTypeDefSchema(param.type); + } else if (isEnum(this.schema, param.type)) { + schema = this.makeEnumSchema(param.type); + } else if (param.type in (this.schema.models ?? {})) { + // For model-typed values, accept any object (no deep shape validation). + schema = z.record(z.string(), z.unknown()); + } else { + // Builtin scalar types. + schema = this.makeScalarSchema(param.type as BuiltinType); + + // If a type isn't recognized by any of the above branches, `makeScalarSchema` returns `unknown`. + // Treat it as configuration/schema error. + if (schema instanceof z.ZodUnknown) { + throw createInternalError(`Unsupported procedure parameter type: ${param.type}`); + } + } + + if (param.array) { + schema = schema.array(); + } + if (param.optional) { + schema = schema.optional(); + } + + return schema; + } + + // #endregion + + // #region Plugin Args + + private mergePluginArgsSchema(schema: ZodObject, operation: CoreCrudOperations) { + let result = schema; + for (const plugin of this.plugins ?? []) { + if (plugin.queryArgs) { + const pluginSchema = this.getPluginExtQueryArgsSchema(plugin, operation); + if (pluginSchema) { + result = result.extend(pluginSchema.shape); + } + } + } + return result.strict(); + } + + private getPluginExtQueryArgsSchema(plugin: AnyPlugin, operation: string): ZodObject | undefined { + if (!plugin.queryArgs) { + return undefined; + } + + let result: ZodType | undefined; + + if (operation in plugin.queryArgs && plugin.queryArgs[operation]) { + // most specific operation takes highest precedence + result = plugin.queryArgs[operation]; + } else if (operation === 'upsert') { + // upsert is special: it's in both CoreCreateOperations and CoreUpdateOperations + // so we need to merge both $create and $update schemas to match the type system + const createSchema = + '$create' in plugin.queryArgs && plugin.queryArgs['$create'] ? plugin.queryArgs['$create'] : undefined; + const updateSchema = + '$update' in plugin.queryArgs && plugin.queryArgs['$update'] ? plugin.queryArgs['$update'] : undefined; + + if (createSchema && updateSchema) { + invariant(createSchema instanceof ZodObject, 'Plugin extended query args schema must be a Zod object'); + invariant(updateSchema instanceof ZodObject, 'Plugin extended query args schema must be a Zod object'); + // merge both schemas (combines their properties) + result = createSchema.extend(updateSchema.shape); + } else if (createSchema) { + result = createSchema; + } else if (updateSchema) { + result = updateSchema; + } + } else if ( + // then comes grouped operations: $create, $read, $update, $delete + CoreCreateOperations.includes(operation as CoreCreateOperations) && + '$create' in plugin.queryArgs && + plugin.queryArgs['$create'] + ) { + result = plugin.queryArgs['$create']; + } else if ( + CoreReadOperations.includes(operation as CoreReadOperations) && + '$read' in plugin.queryArgs && + plugin.queryArgs['$read'] + ) { + result = plugin.queryArgs['$read']; + } else if ( + CoreUpdateOperations.includes(operation as CoreUpdateOperations) && + '$update' in plugin.queryArgs && + plugin.queryArgs['$update'] + ) { + result = plugin.queryArgs['$update']; + } else if ( + CoreDeleteOperations.includes(operation as CoreDeleteOperations) && + '$delete' in plugin.queryArgs && + plugin.queryArgs['$delete'] + ) { + result = plugin.queryArgs['$delete']; + } else if ('$all' in plugin.queryArgs && plugin.queryArgs['$all']) { + // finally comes $all + result = plugin.queryArgs['$all']; + } + + invariant( + result === undefined || result instanceof ZodObject, + 'Plugin extended query args schema must be a Zod object', + ); + return result; + } + + // #endregion + + // #region Helpers + + @cache() + private makeSkipSchema() { + return z.number().int().nonnegative(); + } + + @cache() + private makeTakeSchema() { + return z.number().int(); + } + + private refineForSelectIncludeMutuallyExclusive(schema: ZodType) { + return schema.refine( + (value: any) => !(value['select'] && value['include']), + '"select" and "include" cannot be used together', + ); + } + + private refineForSelectOmitMutuallyExclusive(schema: ZodType) { + return schema.refine( + (value: any) => !(value['select'] && value['omit']), + '"select" and "omit" cannot be used together', + ); + } + + private nullableIf(schema: ZodType, nullable: boolean) { + return nullable ? schema.nullable() : schema; + } + + private orArray(schema: T, canBeArray: boolean) { + return canBeArray ? z.union([schema, z.array(schema)]) : schema; + } + + private isNumericField(fieldDef: FieldDef) { + return NUMERIC_FIELD_TYPES.includes(fieldDef.type) && !fieldDef.array; + } + + private get providerSupportsCaseSensitivity() { + return this.schema.provider.type === 'postgresql'; + } + + /** + * Gets the effective set of allowed FilterKind values for a specific model and field. + * Respects the precedence: model[field] > model.$all > $all[field] > $all.$all. + */ + private getEffectiveFilterKinds(model: string | undefined, field: string): string[] | undefined { + if (!model) { + // no restrictions + return undefined; + } + + const slicing = this.options.slicing; + if (!slicing?.models) { + // no slicing or no model-specific slicing, no restrictions + return undefined; + } + + // A string-indexed view of slicing.models that avoids unsafe 'as any' while still + // allowing runtime access by model name. The value shape matches FieldSlicingOptions. + type FieldConfig = { includedFilterKinds?: readonly string[]; excludedFilterKinds?: readonly string[] }; + type FieldsRecord = { $all?: FieldConfig } & Record; + type ModelConfig = { fields?: FieldsRecord }; + const modelsRecord = slicing.models as Record; + + // Check field-level settings for the specific model + const modelConfig = modelsRecord[lowerCaseFirst(model)]; + if (modelConfig?.fields) { + const fieldConfig = modelConfig.fields[field]; + if (fieldConfig) { + return this.computeFilterKinds(fieldConfig.includedFilterKinds, fieldConfig.excludedFilterKinds); + } + + // Fallback to field-level $all for the specific model + const allFieldsConfig = modelConfig.fields['$all']; + if (allFieldsConfig) { + return this.computeFilterKinds( + allFieldsConfig.includedFilterKinds, + allFieldsConfig.excludedFilterKinds, + ); + } + } + + // Fallback to model-level $all + const allModelsConfig = modelsRecord['$all']; + if (allModelsConfig?.fields) { + // Check specific field in $all model config before falling back to $all.$all + const allModelsFieldConfig = allModelsConfig.fields[field]; + if (allModelsFieldConfig) { + return this.computeFilterKinds( + allModelsFieldConfig.includedFilterKinds, + allModelsFieldConfig.excludedFilterKinds, + ); + } + + // Fallback to $all.$all + const allModelsAllFieldsConfig = allModelsConfig.fields['$all']; + if (allModelsAllFieldsConfig) { + return this.computeFilterKinds( + allModelsAllFieldsConfig.includedFilterKinds, + allModelsAllFieldsConfig.excludedFilterKinds, + ); + } + } + + return undefined; // No restrictions + } + + /** + * Computes the effective set of filter kinds based on inclusion and exclusion lists. + */ + private computeFilterKinds(included: readonly string[] | undefined, excluded: readonly string[] | undefined) { + let result: string[] | undefined; + + if (included !== undefined) { + // Start with the included set + result = [...included]; + } + + if (excluded !== undefined) { + if (!result) { + // If no inclusion list, start with all filter kinds + result = [...this.allFilterKinds]; + } + // Remove excluded kinds + for (const kind of excluded) { + result = result.filter((k) => k !== kind); + } + } + + return result; + } + + /** + * Filters operators based on allowed filter kinds. + */ + private trimFilterOperators>( + operators: T, + allowedKinds: string[] | undefined, + ): Partial { + if (!allowedKinds) { + return operators; // No restrictions + } + + return Object.fromEntries( + Object.entries(operators).filter(([key, _]) => { + return ( + !(key in FILTER_PROPERTY_TO_KIND) || + allowedKinds.includes(FILTER_PROPERTY_TO_KIND[key as keyof typeof FILTER_PROPERTY_TO_KIND]) + ); + }), + ) as Partial; + } + + private createUnionFilterSchema( + valueSchema: ZodType, + optional: boolean, + components: Record, + allowedFilterKinds: string[] | undefined, + ) { + // If all filter operators are excluded + if (Object.keys(components).length === 0) { + // if equality filters are allowed, allow direct value + if (!allowedFilterKinds || allowedFilterKinds.includes('Equality')) { + return this.nullableIf(valueSchema, optional); + } + // otherwise nothing is allowed + return z.never(); + } + + if (!allowedFilterKinds || allowedFilterKinds.includes('Equality')) { + // direct value or filter operators + return z.union([this.nullableIf(valueSchema, optional), z.strictObject(components)]); + } else { + // filter operators + return z.strictObject(components); + } + } + + /** + * Checks if a model is included in the slicing configuration. + * Returns true if the model is allowed, false if it's excluded. + */ + private isModelAllowed(targetModel: string): boolean { + const slicing = this.options.slicing; + if (!slicing) { + return true; // No slicing, all models allowed + } + + const { includedModels, excludedModels } = slicing; + + // If includedModels is specified, only those models are allowed + if (includedModels !== undefined) { + if (!includedModels.includes(targetModel as any)) { + return false; + } + } + + // If excludedModels is specified, those models are not allowed + if (excludedModels !== undefined) { + if (excludedModels.includes(targetModel as any)) { + return false; + } + } + + return true; + } + + // #endregion +} + +export function createSchemaFactory>( + client: Client, +): Client extends ClientContract + ? ZodSchemaFactory + : never { + return new ZodSchemaFactory(client) as any; +} diff --git a/packages/orm/src/client/zod/index.ts b/packages/orm/src/client/zod/index.ts new file mode 100644 index 000000000..63e947e57 --- /dev/null +++ b/packages/orm/src/client/zod/index.ts @@ -0,0 +1 @@ +export { createQuerySchemaFactory } from './factory'; diff --git a/packages/schema/package.json b/packages/schema/package.json index 0af386bd7..2b8b09bec 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -7,7 +7,9 @@ "build": "tsc --noEmit && tsup-node", "watch": "tsup-node --watch", "lint": "eslint src --ext ts", - "pack": "pnpm pack" + "test": "vitest run", + "pack": "pnpm pack", + "test:generate": "tsx ../../scripts/test-generate.ts ." }, "keywords": [], "author": "ZenStack Team", @@ -32,6 +34,7 @@ }, "devDependencies": { "@zenstackhq/eslint-config": "workspace:*", - "@zenstackhq/typescript-config": "workspace:*" + "@zenstackhq/typescript-config": "workspace:*", + "@zenstackhq/vitest-config": "workspace:*" } } diff --git a/packages/schema/src/accessor.ts b/packages/schema/src/accessor.ts new file mode 100644 index 000000000..e732b54f1 --- /dev/null +++ b/packages/schema/src/accessor.ts @@ -0,0 +1,232 @@ +import { ExpressionUtils } from './expression-utils'; +import type { + DataSourceProviderType, + EnumDef, + FieldDef, + ModelDef, + ProcedureDef, + SchemaDef, + TypeDefDef, +} from './schema'; + +type Accessors = { + /** + * The data source provider type of the schema, e.g. "sqlite", "postgresql", etc. + */ + get providerType(): DataSourceProviderType; + + /** + * Gets a model definition by name. Returns `undefined` if the model is not found. + */ + getModel(name: string): ModelDef | undefined; + + /** + * Gets a model definition by name. Throws an error if the model is not found. + */ + requireModel(name: string): ModelDef; + + /** + * Gets a field definition by model/type and field name. Returns `undefined` if the field is not found. + */ + getField(modelOrType: string, field: string): FieldDef | undefined; + + /** + * Gets a field definition by model/type and field name. Throws an error if the field is not found. + */ + requireField(modelOrType: string, field: string): FieldDef; + + /*** + * Gets an enum definition by name. Returns `undefined` if the enum is not found. + */ + getEnum(name: string): EnumDef | undefined; + + /** + * Gets an enum definition by name. Throws an error if the enum is not found. + */ + requireEnum(name: string): EnumDef; + + /** + * Gets a type definition by name. Returns `undefined` if the type definition is not found. + * @param name + */ + getTypeDef(name: string): TypeDefDef | undefined; + + /** + * Gets a type definition by name. Throws an error if the type definition is not found. + */ + requireTypeDef(name: string): TypeDefDef; + + /** + * Gets a procedure definition by name. Returns `undefined` if the procedure is not found. + */ + getProcedure(name: string): ProcedureDef | undefined; + + /** + * Gets a procedure definition by name. Throws an error if the procedure is not found. + */ + requireProcedure(name: string): ProcedureDef; + + /** + * Gets the unique fields of a model, including both singular and compound unique fields. + */ + getUniqueFields( + model: string, + ): Array<{ name: string; def: FieldDef } | { name: string; defs: Record }>; + + /** + * Gets the delegate discriminator field for a model, if defined via `@@delegate` attribute. Returns `undefined` if not available. + */ + getDelegateDiscriminator(model: string): string | undefined; +}; + +export class InvalidSchemaError extends Error { + constructor(message: string) { + super(message); + } +} + +type AccessorTarget = { schema: SchemaDef }; + +function _requireModel(schema: SchemaDef, name: string): ModelDef { + const model = schema.models[name]; + if (!model) throw new InvalidSchemaError(`Model "${name}" not found in schema`); + return model; +} + +function _getField(schema: SchemaDef, modelOrType: string, field: string): FieldDef | undefined { + const modelDef = schema.models?.[modelOrType]; + if (modelDef) { + return modelDef.fields[field]; + } + const typeDef = schema.typeDefs?.[modelOrType]; + if (typeDef) { + return typeDef.fields[field]; + } + return undefined; +} + +function _requireField(schema: SchemaDef, modelOrType: string, field: string): FieldDef { + const fieldDef = _getField(schema, modelOrType, field); + if (!fieldDef) throw new InvalidSchemaError(`Field "${modelOrType}.${field}" not found in schema`); + return fieldDef; +} + +function _requireModelField(schema: SchemaDef, model: string, field: string) { + const modelDef = _requireModel(schema, model); + const fieldDef = modelDef.fields[field]; + if (!fieldDef) throw new InvalidSchemaError(`Field "${model}.${field}" not found in schema`); + return fieldDef; +} + +const accessors: Accessors = { + get providerType() { + return (this as unknown as AccessorTarget).schema.provider.type; + }, + + getModel(this: { schema: SchemaDef }, name: string) { + return this.schema.models[name]; + }, + + requireModel(this: { schema: SchemaDef }, name: string) { + return _requireModel(this.schema, name); + }, + + getField(this: { schema: SchemaDef }, modelOrType: string, field: string) { + return _getField(this.schema, modelOrType, field); + }, + + requireField(this: { schema: SchemaDef }, modelOrType: string, field: string) { + return _requireField(this.schema, modelOrType, field); + }, + + getEnum(this: { schema: SchemaDef }, name: string) { + return this.schema.enums?.[name]; + }, + + requireEnum(this: { schema: SchemaDef }, name: string) { + const enumDef = this.schema.enums?.[name]; + if (!enumDef) throw new InvalidSchemaError(`Enum "${name}" not found in schema`); + return enumDef; + }, + + getTypeDef(this: { schema: SchemaDef }, name: string) { + return this.schema.typeDefs?.[name]; + }, + + requireTypeDef(this: { schema: SchemaDef }, name: string) { + const typeDef = this.schema.typeDefs?.[name]; + if (!typeDef) throw new InvalidSchemaError(`TypeDef "${name}" not found in schema`); + return typeDef; + }, + + getProcedure(this: { schema: SchemaDef }, name: string) { + return this.schema.procedures?.[name]; + }, + + requireProcedure(this: { schema: SchemaDef }, name: string) { + const procedure = this.schema.procedures?.[name]; + if (!procedure) throw new InvalidSchemaError(`Procedure "${name}" not found in schema`); + return procedure; + }, + + getUniqueFields(this: { schema: SchemaDef }, model: string) { + const modelDef = _requireModel(this.schema, model); + const result: Array<{ name: string; def: FieldDef } | { name: string; defs: Record }> = []; + for (const [key, value] of Object.entries(modelDef.uniqueFields)) { + if (value === null || typeof value !== 'object') { + throw new InvalidSchemaError(`Invalid unique field definition for "${model}.${key}"`); + } + + if (typeof value.type === 'string') { + // singular unique field + result.push({ name: key, def: _requireModelField(this.schema, model, key) }); + } else { + // compound unique field + result.push({ + name: key, + defs: Object.fromEntries( + Object.keys(value).map((k) => [k, _requireModelField(this.schema, model, k)]), + ), + }); + } + } + return result; + }, + + getDelegateDiscriminator(this: { schema: SchemaDef }, model: string) { + const modelDef = _requireModel(this.schema, model); + const delegateAttr = modelDef.attributes?.find((attr) => attr.name === '@@delegate'); + if (!delegateAttr) { + return undefined; + } + const discriminator = delegateAttr.args?.find((arg) => arg.name === 'discriminator'); + if (!discriminator || !ExpressionUtils.isField(discriminator.value)) { + throw new InvalidSchemaError(`Discriminator field not defined for model "${model}"`); + } + return discriminator.value.field; + }, +}; + +export type SchemaAccessor = Schema & Accessors; + +export interface SchemaAccessorConstructor { + new (schema: Schema): SchemaAccessor; +} + +export const SchemaAccessor = function (this: any, schema: Schema) { + return new Proxy( + { schema }, + { + get(target, prop) { + const descriptor = Object.getOwnPropertyDescriptor(accessors, prop); + if (descriptor?.get) { + return descriptor.get.call(target); + } + if (prop in accessors) { + return (accessors as any)[prop].bind(target); + } + return (schema as any)[prop]; + }, + }, + ); +} as unknown as SchemaAccessorConstructor; diff --git a/packages/schema/src/index.ts b/packages/schema/src/index.ts index 6a171e592..bb954c094 100644 --- a/packages/schema/src/index.ts +++ b/packages/schema/src/index.ts @@ -1,3 +1,4 @@ +export { InvalidSchemaError, SchemaAccessor } from './accessor'; export type * from './expression'; export * from './expression-utils'; export type * from './schema'; diff --git a/packages/schema/src/schema.ts b/packages/schema/src/schema.ts index d98b86f01..e21b5e30e 100644 --- a/packages/schema/src/schema.ts +++ b/packages/schema/src/schema.ts @@ -18,18 +18,18 @@ export type SchemaDef = { authType?: GetModels | GetTypeDefs; }; +export type UniqueFieldsInfo = + // singular unique field + | Pick + // compound unique field + | Record>; + export type ModelDef = { name: string; baseModel?: string; fields: Record; attributes?: readonly AttributeApplication[]; - uniqueFields: Record< - string, - // singular unique field - | Pick - // compound unique field - | Record> - >; + uniqueFields: Record; idFields: readonly string[]; computedFields?: Record; isDelegate?: boolean; @@ -139,7 +139,7 @@ export type GetSubModels> = Schema['models'][Model]; -export type GetEnums = keyof Schema['enums']; +export type GetEnums = Extract; export type GetEnum> = Schema['enums'][Enum] extends EnumDef ? Schema['enums'][Enum]['values'] @@ -287,7 +287,7 @@ export type FieldHasDefault< Field extends GetModelFields, > = GetModelField['default'] extends object | number | string | boolean ? true - : GetModelField['updatedAt'] extends (true | UpdatedAtInfo) + : GetModelField['updatedAt'] extends true | UpdatedAtInfo ? true : GetModelField['relation'] extends { hasDefault: true } ? true diff --git a/packages/schema/test/accessor.test.ts b/packages/schema/test/accessor.test.ts new file mode 100644 index 000000000..dd15af2f6 --- /dev/null +++ b/packages/schema/test/accessor.test.ts @@ -0,0 +1,117 @@ +import { describe, expect, it } from 'vitest'; +import { InvalidSchemaError, SchemaAccessor } from '../src/accessor'; +import { schema } from './schema/schema'; + +describe('SchemaAccessor tests', () => { + const accessor = new SchemaAccessor(schema); + + it('proxies schema properties through', () => { + expect(accessor.provider).toEqual({ type: 'sqlite' }); + expect(accessor.models).toBe(schema.models); + expect(accessor.authType).toBe('User'); + }); + + it('returns providerType', () => { + expect(accessor.providerType).toBe('sqlite'); + }); + + it('getModel returns model if found', () => { + expect(accessor.getModel('User')).toBe(schema.models.User); + expect(accessor.getModel('Post')).toBe(schema.models.Post); + }); + + it('getModel returns undefined for unknown model', () => { + expect(accessor.getModel('Unknown')).toBeUndefined(); + }); + + it('requireModel returns model if found', () => { + expect(accessor.requireModel('User')).toBe(schema.models.User); + }); + + it('requireModel throws for unknown model', () => { + expect(() => accessor.requireModel('Unknown')).toThrow(InvalidSchemaError); + expect(() => accessor.requireModel('Unknown')).toThrow('Model "Unknown" not found in schema'); + }); + + it('getEnum returns enum if found', () => { + expect(accessor.getEnum('Role')).toBe(schema.enums.Role); + }); + + it('getEnum returns undefined for unknown enum', () => { + expect(accessor.getEnum('Unknown')).toBeUndefined(); + }); + + it('requireEnum returns enum if found', () => { + const enumDef = accessor.requireEnum('Role'); + expect(enumDef.name).toBe('Role'); + expect(enumDef.values).toEqual({ ADMIN: 'ADMIN', USER: 'USER' }); + }); + + it('requireEnum throws for unknown enum', () => { + expect(() => accessor.requireEnum('Unknown')).toThrow(InvalidSchemaError); + expect(() => accessor.requireEnum('Unknown')).toThrow('Enum "Unknown" not found in schema'); + }); + + it('getTypeDef returns typeDef if found', () => { + expect(accessor.getTypeDef('Address')).toBe(schema.typeDefs.Address); + }); + + it('getTypeDef returns undefined for unknown typeDef', () => { + expect(accessor.getTypeDef('Unknown')).toBeUndefined(); + }); + + it('requireTypeDef returns typeDef if found', () => { + const typeDef = accessor.requireTypeDef('Address'); + expect(typeDef.name).toBe('Address'); + expect(typeDef.fields.street).toMatchObject({ name: 'street', type: 'String' }); + expect(typeDef.fields.city).toMatchObject({ name: 'city', type: 'String' }); + expect(typeDef.fields.zip).toMatchObject({ name: 'zip', type: 'String', optional: true }); + }); + + it('requireTypeDef throws for unknown typeDef', () => { + expect(() => accessor.requireTypeDef('Unknown')).toThrow(InvalidSchemaError); + expect(() => accessor.requireTypeDef('Unknown')).toThrow('TypeDef "Unknown" not found in schema'); + }); + + it('getProcedure returns procedure if found', () => { + expect(accessor.getProcedure('getUserPosts')).toBe(schema.procedures.getUserPosts); + }); + + it('getProcedure returns undefined for unknown procedure', () => { + expect(accessor.getProcedure('unknown')).toBeUndefined(); + }); + + it('requireProcedure returns procedure if found', () => { + const proc = accessor.requireProcedure('getUserPosts'); + expect(proc.returnType).toBe('Post'); + expect(proc.returnArray).toBe(true); + expect(proc.params.userId).toMatchObject({ name: 'userId', type: 'String' }); + }); + + it('requireProcedure throws for unknown procedure', () => { + expect(() => accessor.requireProcedure('unknown')).toThrow(InvalidSchemaError); + expect(() => accessor.requireProcedure('unknown')).toThrow('Procedure "unknown" not found in schema'); + }); + + it('getUniqueFields returns singular unique fields for User', () => { + const fields = accessor.getUniqueFields('User'); + const names = fields.map((f) => f.name); + expect(names).toContain('id'); + expect(names).toContain('email'); + // each entry should be a singular field with a `def` + for (const f of fields) { + expect('def' in f).toBe(true); + } + }); + + it('getUniqueFields returns singular unique field for Post', () => { + const fields = accessor.getUniqueFields('Post'); + expect(fields).toHaveLength(1); + expect(fields[0]!.name).toBe('id'); + expect('def' in fields[0]!).toBe(true); + }); + + it('getUniqueFields throws for unknown model', () => { + expect(() => accessor.getUniqueFields('Unknown')).toThrow(InvalidSchemaError); + }); +}); diff --git a/packages/schema/test/schema/schema.ts b/packages/schema/test/schema/schema.ts new file mode 100644 index 000000000..846028a79 --- /dev/null +++ b/packages/schema/test/schema/schema.ts @@ -0,0 +1,135 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], + default: ExpressionUtils.call("cuid") + }, + email: { + name: "email", + type: "String", + unique: true, + attributes: [{ name: "@unique" }] + }, + name: { + name: "name", + type: "String", + optional: true + }, + role: { + name: "role", + type: "Role" + }, + address: { + name: "address", + type: "Address", + optional: true, + attributes: [{ name: "@json" }] + }, + posts: { + name: "posts", + type: "Post", + array: true, + relation: { opposite: "owner" } + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" }, + email: { type: "String" } + } + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], + default: ExpressionUtils.call("cuid") + }, + title: { + name: "title", + type: "String" + }, + owner: { + name: "owner", + type: "User", + optional: true, + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array("String", [ExpressionUtils.field("ownerId")]) }, { name: "references", value: ExpressionUtils.array("String", [ExpressionUtils.field("id")]) }] }], + relation: { opposite: "posts", fields: ["ownerId"], references: ["id"] } + }, + ownerId: { + name: "ownerId", + type: "String", + optional: true, + foreignKeyFor: [ + "owner" + ] + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + } + } as const; + typeDefs = { + Address: { + name: "Address", + fields: { + street: { + name: "street", + type: "String" + }, + city: { + name: "city", + type: "String" + }, + zip: { + name: "zip", + type: "String", + optional: true + } + } + } + } as const; + enums = { + Role: { + name: "Role", + values: { + ADMIN: "ADMIN", + USER: "USER" + } + } + } as const; + authType = "User" as const; + procedures = { + getUserPosts: { + params: { + userId: { name: "userId", type: "String" } + }, + returnType: "Post", + returnArray: true + } + } as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/packages/schema/test/schema/schema.zmodel b/packages/schema/test/schema/schema.zmodel new file mode 100644 index 000000000..b611fa663 --- /dev/null +++ b/packages/schema/test/schema/schema.zmodel @@ -0,0 +1,32 @@ +datasource db { + provider = 'sqlite' +} + +type Address { + street String + city String + zip String? +} + +enum Role { + ADMIN + USER +} + +model User { + id String @id @default(cuid()) + email String @unique + name String? + role Role + address Address? @json + posts Post[] +} + +model Post { + id String @id @default(cuid()) + title String + owner User? @relation(fields: [ownerId], references: [id]) + ownerId String? +} + +procedure getUserPosts(userId: String): Post[] diff --git a/packages/schema/vitest.config.ts b/packages/schema/vitest.config.ts new file mode 100644 index 000000000..75a9f709c --- /dev/null +++ b/packages/schema/vitest.config.ts @@ -0,0 +1,4 @@ +import base from '@zenstackhq/vitest-config/base'; +import { defineConfig, mergeConfig } from 'vitest/config'; + +export default mergeConfig(base, defineConfig({})); diff --git a/packages/zod/package.json b/packages/zod/package.json index a146e20e9..a7488a784 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -5,9 +5,16 @@ "type": "module", "scripts": { "build": "tsc --noEmit && tsup-node", - "lint": "eslint src --ext ts" + "watch": "tsup-node --watch", + "lint": "eslint src --ext ts", + "test": "vitest run", + "pack": "pnpm pack", + "test:generate": "tsx ../../scripts/test-generate.ts ." }, - "keywords": [], + "keywords": [ + "zenstack", + "zod" + ], "files": [ "dist" ], @@ -26,12 +33,16 @@ } }, "dependencies": { - "@zenstackhq/orm": "workspace:*", + "@zenstackhq/common-helpers": "workspace:*", + "@zenstackhq/schema": "workspace:*", + "decimal.js": "catalog:", + "json-stable-stringify": "^1.3.0", "ts-pattern": "catalog:" }, "devDependencies": { "@zenstackhq/eslint-config": "workspace:*", "@zenstackhq/typescript-config": "workspace:*", + "@zenstackhq/vitest-config": "workspace:*", "zod": "^4.1.0" }, "peerDependencies": { diff --git a/packages/zod/src/error.ts b/packages/zod/src/error.ts new file mode 100644 index 000000000..f55dbbba1 --- /dev/null +++ b/packages/zod/src/error.ts @@ -0,0 +1,8 @@ +/** + * Error representing failures in Zod schema building. + */ +export class ZodSchemaError extends Error { + constructor(message: string) { + super(message); + } +} diff --git a/packages/zod/src/factory.ts b/packages/zod/src/factory.ts new file mode 100644 index 000000000..02e1ab031 --- /dev/null +++ b/packages/zod/src/factory.ts @@ -0,0 +1,243 @@ +import { + SchemaAccessor, + type BuiltinType, + type FieldDef, + type FieldIsArray, + type FieldIsRelation, + type GetEnum, + type GetEnums, + type GetModelFields, + type GetModelFieldType, + type GetModels, + type GetTypeDefFields, + type GetTypeDefFieldType, + type GetTypeDefs, + type ModelFieldIsOptional, + type SchemaDef, + type TypeDefFieldIsOptional, +} from '@zenstackhq/schema'; +import Decimal from 'decimal.js'; +import { match } from 'ts-pattern'; +import z from 'zod'; +import { + addBigIntValidation, + addCustomValidation, + addDecimalValidation, + addNumberValidation, + addStringValidation, +} from './utils'; + +export function createSchemaFactory(schema: Schema) { + return new SchemaFactory(schema); +} + +class SchemaFactory { + private readonly schema: SchemaAccessor; + + constructor(_schema: Schema) { + this.schema = new SchemaAccessor(_schema); + } + + makeModelSchema>( + model: Model, + ): z.ZodObject, z.core.$strict> { + const modelDef = this.schema.models[model]; + if (!modelDef) { + throw new Error(`Model "${model}" not found in schema`); + } + const fields: Record = {}; + + for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { + if (fieldDef.relation) { + const relatedModelName = fieldDef.type; + const lazySchema: z.ZodType = z.lazy(() => this.makeModelSchema(relatedModelName as GetModels)); + // relation fields are always optional + fields[fieldName] = this.applyCardinality(lazySchema, fieldDef).optional(); + } else { + fields[fieldName] = this.makeScalarFieldSchema(fieldDef); + } + } + + const shape = z.strictObject(fields); + return addCustomValidation(shape, modelDef.attributes) as unknown as z.ZodObject< + GetModelFieldsShape, + z.core.$strict + >; + } + + private makeScalarFieldSchema(fieldDef: FieldDef): z.ZodType { + const { type, attributes } = fieldDef; + + // enum + const enumDef = this.schema.getEnum(type); + if (enumDef) { + return this.applyCardinality(this.makeEnumSchema(type as GetEnums), fieldDef); + } + + // typedef + const typedefDef = this.schema.getTypeDef(type); + if (typedefDef) { + return this.applyCardinality(this.makeTypeSchema(type as GetTypeDefs), fieldDef); + } + + const base = match(type as BuiltinType) + .with('String', () => addStringValidation(z.string(), attributes)) + .with('Int', () => addNumberValidation(z.number().int(), attributes)) + .with('Float', () => addNumberValidation(z.number(), attributes)) + .with('Boolean', () => z.boolean()) + .with('BigInt', () => addBigIntValidation(z.bigint(), attributes)) + .with('Decimal', () => + z.union([ + addNumberValidation(z.number(), attributes) as z.ZodNumber, + addDecimalValidation(z.string(), attributes, true) as z.ZodString, + addDecimalValidation(z.instanceof(Decimal), attributes, true), + ]), + ) + .with('DateTime', () => z.union([z.date(), z.iso.datetime()])) + .with('Bytes', () => z.instanceof(Uint8Array)) + .with('Json', () => this.makeJsonSchema()) + .with('Unsupported', () => z.unknown()) + .exhaustive(); + + return this.applyCardinality(base, fieldDef); + } + + private makeJsonSchema(): z.ZodType { + return z.union([ + z.string(), + z.number(), + z.boolean(), + z.null(), + z.array(z.lazy(() => this.makeJsonSchema())), + z.object({}).catchall(z.lazy(() => this.makeJsonSchema())), + ]); + } + + private applyCardinality(schema: z.ZodType, fieldDef: FieldDef): z.ZodType { + let result = schema; + if (fieldDef.array) { + result = result.array(); + } + if (fieldDef.optional) { + result = result.nullable().optional(); + } + return result; + } + + makeTypeSchema>( + type: Type, + ): z.ZodObject, z.core.$strict> { + const typeDef = this.schema.requireTypeDef(type); + const fields: Record = {}; + + for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { + fields[fieldName] = this.makeScalarFieldSchema(fieldDef); + } + + const shape = z.strictObject(fields); + return addCustomValidation(shape, typeDef.attributes) as unknown as z.ZodObject< + GetTypeDefFieldsShape, + z.core.$strict + >; + } + + makeEnumSchema>( + _enum: Enum, + ): z.ZodEnum<{ [Key in keyof GetEnum]: GetEnum[Key] }> { + const enumDef = this.schema.requireEnum(_enum); + return z.enum(Object.keys(enumDef.values) as [string, ...string[]]) as unknown as z.ZodEnum<{ + [Key in keyof GetEnum]: GetEnum[Key]; + }>; + } +} + +type GetModelFieldsShape> = { + // scalar fields + [Field in GetModelFields as FieldIsRelation extends true + ? never + : Field]: ZodOptionalAndNullableIf< + MapModelFieldToZod, + ModelFieldIsOptional + >; +} & { + // relation fields, always optional + [Field in GetModelFields as FieldIsRelation extends true + ? Field + : never]: ZodNullableIf< + z.ZodOptional< + ZodArrayIf< + z.ZodObject< + GetModelFieldsShape< + Schema, + GetModelFieldType extends GetModels + ? GetModelFieldType + : never + >, + z.core.$strict + >, + FieldIsArray + > + >, + ModelFieldIsOptional + >; +}; + +type GetTypeDefFieldsShape> = { + [Field in GetTypeDefFields]: ZodOptionalAndNullableIf< + MapTypeDefFieldToZod, + TypeDefFieldIsOptional + >; +}; + +type FieldTypeZodMap = { + String: z.ZodString; + Int: z.ZodNumber; + BigInt: z.ZodBigInt; + Float: z.ZodNumber; + Decimal: z.ZodType; + Boolean: z.ZodBoolean; + DateTime: z.ZodType; + Bytes: z.ZodType; + Json: JsonZodType; +}; + +type MapModelFieldToZod< + Schema extends SchemaDef, + Model extends GetModels, + Field extends GetModelFields, + FieldType = GetModelFieldType, +> = MapFieldTypeToZod; + +type MapTypeDefFieldToZod< + Schema extends SchemaDef, + Type extends GetTypeDefs, + Field extends GetTypeDefFields, + FieldType = GetTypeDefFieldType, +> = MapFieldTypeToZod; + +type MapFieldTypeToZod = FieldType extends keyof FieldTypeZodMap + ? FieldTypeZodMap[FieldType] + : FieldType extends GetEnums + ? EnumZodType + : FieldType extends GetTypeDefs + ? z.ZodObject, z.core.$strict> + : z.ZodUnknown; + +type JsonZodType = + | z.ZodObject, z.core.$loose> + | z.ZodArray + | z.ZodString + | z.ZodNumber + | z.ZodBoolean + | z.ZodNull; + +type EnumZodType> = z.ZodEnum<{ + [Key in keyof GetEnum]: GetEnum[Key]; +}>; + +type ZodOptionalAndNullableIf = Condition extends true + ? z.ZodOptional> + : T; + +type ZodNullableIf = Condition extends true ? z.ZodNullable : T; +type ZodArrayIf = Condition extends true ? z.ZodArray : T; diff --git a/packages/zod/src/index.ts b/packages/zod/src/index.ts index 8211af368..905551618 100644 --- a/packages/zod/src/index.ts +++ b/packages/zod/src/index.ts @@ -1,33 +1,2 @@ -import type { FieldDef, GetModels, SchemaDef } from '@zenstackhq/orm/schema'; -import { match, P } from 'ts-pattern'; -import { z, ZodType } from 'zod'; -import type { SelectSchema } from './types'; - -export function makeSelectSchema>( - schema: Schema, - model: Model, -) { - return z.strictObject(mapFields(schema, model)) as SelectSchema; -} - -function mapFields(schema: Schema, model: GetModels): any { - const modelDef = schema.models[model]; - if (!modelDef) { - throw new Error(`Model ${model} not found in schema`); - } - const scalarFields = Object.entries(modelDef.fields).filter(([_, fieldDef]) => !fieldDef.relation); - const result: Record = {}; - for (const [field, fieldDef] of scalarFields) { - result[field] = makeScalarSchema(fieldDef); - } - return result; -} - -function makeScalarSchema(fieldDef: FieldDef): ZodType { - return match(fieldDef.type) - .with('String', () => z.string()) - .with(P.union('Int', 'BigInt', 'Float', 'Decimal'), () => z.number()) - .with('Boolean', () => z.boolean()) - .with('DateTime', () => z.string().datetime()) - .otherwise(() => z.unknown()); -} +export { createSchemaFactory as createModelSchemaFactory } from './factory'; +export * as ZodUtils from './utils'; diff --git a/packages/zod/src/types.ts b/packages/zod/src/types.ts deleted file mode 100644 index 249c6de9e..000000000 --- a/packages/zod/src/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -import type { FieldType, GetModels, ScalarFields, SchemaDef } from '@zenstackhq/orm/schema'; -import type { ZodBoolean, ZodNumber, ZodObject, ZodString, ZodUnknown } from 'zod'; - -export type SelectSchema> = ZodObject<{ - [Key in ScalarFields]: MapScalarType; -}>; - -type MapScalarType< - Schema extends SchemaDef, - Model extends GetModels, - Field extends ScalarFields, - Type = FieldType, -> = Type extends 'String' - ? ZodString - : Type extends 'Int' - ? ZodNumber - : Type extends 'BigInt' - ? ZodNumber - : Type extends 'Float' - ? ZodNumber - : Type extends 'Decimal' - ? ZodNumber - : Type extends 'DateTime' - ? ZodString - : Type extends 'Boolean' - ? ZodBoolean - : ZodUnknown; diff --git a/packages/orm/src/client/crud/validator/utils.ts b/packages/zod/src/utils.ts similarity index 96% rename from packages/orm/src/client/crud/validator/utils.ts rename to packages/zod/src/utils.ts index c9909a702..b670c7316 100644 --- a/packages/orm/src/client/crud/validator/utils.ts +++ b/packages/zod/src/utils.ts @@ -1,19 +1,18 @@ import { invariant } from '@zenstackhq/common-helpers'; -import type { - AttributeApplication, - BinaryExpression, - CallExpression, - Expression, - FieldExpression, - MemberExpression, - UnaryExpression, +import { + ExpressionUtils, + type AttributeApplication, + type BinaryExpression, + type CallExpression, + type Expression, + type FieldExpression, + type MemberExpression, + type UnaryExpression, } from '@zenstackhq/schema'; import Decimal from 'decimal.js'; import { match, P } from 'ts-pattern'; import { z } from 'zod'; -import { ZodIssueCode } from 'zod/v3'; -import { ExpressionUtils } from '../../../schema'; -import { createNotSupportedError } from '../../errors'; +import { ZodSchemaError } from './error'; function getArgValue(expr: Expression | undefined): T | undefined { if (!expr || !ExpressionUtils.isLiteral(expr)) { @@ -167,7 +166,7 @@ export function addDecimalValidation( new Decimal(v); } catch (err) { ctx.addIssue({ - code: z.ZodIssueCode.custom, + code: 'custom', message: `Invalid decimal: ${err}`, }); } @@ -184,7 +183,7 @@ export function addDecimalValidation( error?.issues.forEach((issue) => { if (op === 'gt' || op === 'gte') { ctx.addIssue({ - code: ZodIssueCode.too_small, + code: 'too_small', origin: 'number', minimum: value, type: 'decimal', @@ -193,7 +192,7 @@ export function addDecimalValidation( }); } else { ctx.addIssue({ - code: ZodIssueCode.too_big, + code: 'too_big', origin: 'number', maximum: value, type: 'decimal', @@ -467,7 +466,7 @@ function evalCall(data: any, expr: CallExpression) { return fieldArg.length === 0; }) .otherwise(() => { - throw createNotSupportedError(`Unsupported function "${expr.function}"`); + throw new ZodSchemaError(`Unsupported function "${expr.function}"`); }) ); } diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts new file mode 100644 index 000000000..7283e1d91 --- /dev/null +++ b/packages/zod/test/factory.test.ts @@ -0,0 +1,581 @@ +import Decimal from 'decimal.js'; +import { describe, expect, expectTypeOf, it } from 'vitest'; +import { createModelSchemaFactory } from '../src/index'; +import { schema } from './schema/schema'; +import z from 'zod'; + +const factory = createModelSchemaFactory(schema); + +// A fully valid User object (without relations) +const validUser = { + id: 'user123', + email: 'test@example.com', + username: 'johndoe', + website: null, + code: 'USR001', + age: 25, + score: 50.0, + bigNum: BigInt(100), + balance: 10.0, + active: true, + birthdate: null, + avatar: null, + metadata: null, + status: 'ACTIVE', + address: null, +}; + +// A fully valid Post object (without relations) +const validPost = { + id: 'post123', + title: 'My First Post', + published: true, + authorId: null, +}; + +describe('SchemaFactory - makeModelSchema', () => { + describe('scalar field types', () => { + it('infers correct field types for User', () => { + const _userSchema = factory.makeModelSchema('User'); + type User = z.infer; + + // required string fields + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + // optional string field (nullable + optional) + expectTypeOf().toEqualTypeOf(); + + // number fields (Int and Float both map to ZodNumber) + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + + // bigint + expectTypeOf().toEqualTypeOf(); + + // Decimal maps to ZodCustom + expectTypeOf().toEqualTypeOf(); + + // boolean + expectTypeOf().toEqualTypeOf(); + + // DateTime + expectTypeOf().toEqualTypeOf(); + + // optional Bytes + expectTypeOf().toEqualTypeOf(); + + // optional Json + expectTypeOf().toHaveProperty('metadata'); + expectTypeOf().toEqualTypeOf< + string | number | boolean | null | Record | unknown[] | undefined + >(); + + // required enum + expectTypeOf().toEqualTypeOf<'ACTIVE' | 'INACTIVE' | 'PENDING'>(); + + // optional typedef (Address): { street, city, zip? } | null | undefined + type Address = Exclude; + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf
(); + + // relation field present + expectTypeOf().toHaveProperty('posts'); + const _postSchema = factory.makeModelSchema('Post'); + type Post = z.infer; + expectTypeOf().toEqualTypeOf(); + }); + + it('infers correct field types for Post', () => { + const _postSchema = factory.makeModelSchema('Post'); + type Post = z.infer; + + // required string fields + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + + // required boolean + expectTypeOf().toEqualTypeOf(); + + // optional scalar (foreign key) + expectTypeOf().toEqualTypeOf(); + + // optional relation field present in type + expectTypeOf().toHaveProperty('author'); + const _userSchema = factory.makeModelSchema('User'); + type User = z.infer; + expectTypeOf().toEqualTypeOf(); + }); + + it('accepts a fully valid User', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.safeParse(validUser).success).toBe(true); + }); + + it('accepts a fully valid Post', () => { + const postSchema = factory.makeModelSchema('Post'); + expect(postSchema.safeParse(validPost).success).toBe(true); + }); + + it('rejects extra fields (strict object)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, unknownField: 'value' }); + expect(result.success).toBe(false); + }); + + it('accepts DateTime as a Date object', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, birthdate: new Date() }); + expect(result.success).toBe(true); + }); + + it('accepts DateTime as an ISO datetime string', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ + ...validUser, + birthdate: '2024-01-15T10:30:00.000Z', + }); + expect(result.success).toBe(true); + }); + + it('accepts Bytes as Uint8Array', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ + ...validUser, + avatar: new Uint8Array([1, 2, 3]), + }); + expect(result.success).toBe(true); + }); + + it('accepts BigInt values', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, bigNum: BigInt(999) }); + expect(result.success).toBe(true); + }); + + it('accepts Decimal as a number', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: 42.5 }); + expect(result.success).toBe(true); + }); + + it('accepts Decimal as a numeric string', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: '42.5' }); + expect(result.success).toBe(true); + }); + + it('accepts Decimal as a Decimal instance', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: new Decimal('42.5') }); + expect(result.success).toBe(true); + }); + + it('accepts Json values', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.safeParse({ ...validUser, metadata: { key: 'value' } }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, metadata: [1, 2, 3] }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, metadata: 42 }).success).toBe(true); + }); + + it('rejects invalid Json values', () => { + const userSchema = factory.makeModelSchema('User'); + // BigInt is not a JSON primitive + expect(userSchema.safeParse({ ...validUser, metadata: BigInt(1) }).success).toBe(false); + // Symbol is not a JSON value + expect(userSchema.safeParse({ ...validUser, metadata: Symbol('s') }).success).toBe(false); + // Functions are not JSON values + expect(userSchema.safeParse({ ...validUser, metadata: () => {} }).success).toBe(false); + // Nested non-JSON values are also rejected + expect(userSchema.safeParse({ ...validUser, metadata: { key: BigInt(1) } }).success).toBe(false); + expect(userSchema.safeParse({ ...validUser, metadata: [BigInt(1)] }).success).toBe(false); + }); + }); + + describe('string validation attributes', () => { + it('rejects invalid email for @email field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, email: 'not-an-email' }); + expect(result.success).toBe(false); + }); + + it('accepts valid email for @email field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, email: 'valid@domain.com' }); + expect(result.success).toBe(true); + }); + + it('rejects username too short for @length(3, 50)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, username: 'ab' }); + expect(result.success).toBe(false); + }); + + it('rejects username too long for @length(3, 50)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, username: 'a'.repeat(51) }); + expect(result.success).toBe(false); + }); + + it('accepts username within @length bounds', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.safeParse({ ...validUser, username: 'abc' }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, username: 'a'.repeat(50) }).success).toBe(true); + }); + + it('rejects invalid URL for @url field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, website: 'not-a-url' }); + expect(result.success).toBe(false); + }); + + it('accepts valid URL for @url field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, website: 'https://example.com' }); + expect(result.success).toBe(true); + }); + + it('accepts null for optional @url field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, website: null }); + expect(result.success).toBe(true); + }); + + it('rejects code that does not start with "USR" for @startsWith', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, code: 'ABC001' }); + expect(result.success).toBe(false); + }); + + it('accepts code starting with "USR" for @startsWith', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, code: 'USR_ANYTHING' }); + expect(result.success).toBe(true); + }); + }); + + describe('number validation attributes', () => { + it('rejects age = 0 for @gt(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, age: 0 }); + expect(result.success).toBe(false); + }); + + it('rejects age = 151 for @lte(150)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, age: 151 }); + expect(result.success).toBe(false); + }); + + it('accepts age within @gt(0) and @lte(150) bounds', () => { + const userSchema = factory.makeModelSchema('User'); + // Note: @@validate(age >= 18) also applies, so the minimum valid age is 18 + expect(userSchema.safeParse({ ...validUser, age: 18 }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, age: 150 }).success).toBe(true); + }); + + it('rejects score < 0 for @gte(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, score: -0.1 }); + expect(result.success).toBe(false); + }); + + it('rejects score = 100 for @lt(100)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, score: 100.0 }); + expect(result.success).toBe(false); + }); + + it('accepts score within @gte(0) and @lt(100) bounds', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.safeParse({ ...validUser, score: 0 }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, score: 99.9 }).success).toBe(true); + }); + }); + + describe('bigint validation attributes', () => { + it('rejects bigNum < 0 for @gte(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, bigNum: BigInt(-1) }); + expect(result.success).toBe(false); + }); + + it('accepts bigNum = 0 for @gte(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, bigNum: BigInt(0) }); + expect(result.success).toBe(true); + }); + }); + + describe('decimal validation attributes', () => { + it('rejects balance = 0 (number) for @gt(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: 0 }); + expect(result.success).toBe(false); + }); + + it('rejects balance = "0.0" (string) for @gt(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: '0.0' }); + expect(result.success).toBe(false); + }); + + it('rejects balance = Decimal("0") for @gt(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: new Decimal('0') }); + expect(result.success).toBe(false); + }); + + it('accepts balance = 0.01 (number) for @gt(0)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, balance: 0.01 }); + expect(result.success).toBe(true); + }); + }); + + describe('enum fields', () => { + it('accepts valid enum values', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.safeParse({ ...validUser, status: 'ACTIVE' }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, status: 'INACTIVE' }).success).toBe(true); + expect(userSchema.safeParse({ ...validUser, status: 'PENDING' }).success).toBe(true); + }); + + it('rejects invalid enum value', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, status: 'ADMIN' }); + expect(result.success).toBe(false); + }); + }); + + describe('typedef (embedded type) fields', () => { + it('accepts null for optional typedef field', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, address: null }); + expect(result.success).toBe(true); + }); + + it('accepts valid Address object', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ + ...validUser, + address: { street: '123 Main St', city: 'Springfield', zip: null }, + }); + expect(result.success).toBe(true); + }); + + it('accepts Address with optional zip present', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ + ...validUser, + address: { street: '123 Main St', city: 'Springfield', zip: '12345' }, + }); + expect(result.success).toBe(true); + }); + + it('rejects Address with extra fields (strict object)', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ + ...validUser, + address: { street: '123 Main St', city: 'Springfield', zip: null, extra: 'field' }, + }); + expect(result.success).toBe(false); + }); + + it('rejects Address missing required fields', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ + ...validUser, + address: { street: '123 Main St' }, + }); + expect(result.success).toBe(false); + }); + }); + + describe('@@validate custom validation', () => { + it('fails when @@validate condition is false (age < 18 passes field but fails model validation)', () => { + const userSchema = factory.makeModelSchema('User'); + // age: 16 passes @gt(0) and @lte(150) but fails @@validate(age >= 18) + const result = userSchema.safeParse({ ...validUser, age: 16 }); + expect(result.success).toBe(false); + }); + + it('@@validate error contains the configured message', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, age: 16 }); + expect(result.success).toBe(false); + if (!result.success) { + const messages = result.error.issues.map((i) => i.message); + expect(messages).toContain('Must be adult'); + } + }); + + it('@@validate error uses the configured path', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, age: 16 }); + expect(result.success).toBe(false); + if (!result.success) { + const paths = result.error.issues.map((i) => i.path); + expect(paths).toContainEqual(['age']); + } + }); + + it('passes when @@validate condition is satisfied', () => { + const userSchema = factory.makeModelSchema('User'); + const result = userSchema.safeParse({ ...validUser, age: 18 }); + expect(result.success).toBe(true); + }); + }); + + describe('error handling', () => { + it('throws when model is not found', () => { + expect(() => factory.makeModelSchema('Unknown' as any)).toThrow('Model "Unknown" not found in schema'); + }); + }); +}); + +describe('SchemaFactory - makeTypeSchema', () => { + it('generates schema for Address typedef', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: null }).success).toBe(true); + }); + + it('rejects Address with missing required field', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ street: '123 Main' }); + expect(result.success).toBe(false); + }); + + it('rejects Address with extra fields (strict object)', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ + street: '123 Main', + city: 'Springfield', + zip: null, + extra: 'field', + }); + expect(result.success).toBe(false); + }); + + it('accepts Address with optional zip as null', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: null }).success).toBe(true); + }); + + it('accepts Address with optional zip as a string', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: '12345' }).success).toBe(true); + }); + + describe('extra validations', () => { + it('passes when zip is null', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: null }).success).toBe(true); + }); + + it('passes when zip is omitted', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.safeParse({ street: '123 Main', city: 'Springfield' }).success).toBe(true); + }); + + it('passes when zip is exactly 5 characters', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: '90210' }).success).toBe( + true, + ); + }); + + it('fails when zip is fewer than 5 characters', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: '123' }); + expect(result.success).toBe(false); + }); + + it('fails when zip is more than 5 characters', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: '123456' }); + expect(result.success).toBe(false); + }); + + it('error message matches the configured message', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: '123' }); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues.map((i) => i.message)).toContain('Zip code must be exactly 5 characters'); + } + }); + + it('error path points to the zip field', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ street: '123 Main', city: 'Springfield', zip: '123' }); + expect(result.success).toBe(false); + if (!result.success) { + expect(result.error.issues.map((i) => i.path)).toContainEqual(['zip']); + } + }); + + it('fails when city is too short', () => { + const addressSchema = factory.makeTypeSchema('Address'); + const result = addressSchema.safeParse({ street: '123 Main', city: '', zip: '12345' }); + expect(result.success).toBe(false); + }); + + it('also validates when Address is embedded in User', () => { + const userSchema = factory.makeModelSchema('User'); + const validUser = { + id: 'u1', + email: 'a@b.com', + username: 'alice', + website: null, + code: 'USR01', + age: 20, + score: 50, + bigNum: BigInt(0), + balance: 1, + active: true, + birthdate: null, + avatar: null, + metadata: null, + status: 'ACTIVE', + address: { street: '123 Main', city: 'Springfield', zip: '90210' }, + }; + expect(userSchema.safeParse(validUser).success).toBe(true); + expect( + userSchema.safeParse({ ...validUser, address: { street: '123 Main', city: 'Springfield', zip: '123' } }) + .success, + ).toBe(false); + }); + }); +}); + +describe('SchemaFactory - makeEnumSchema', () => { + it('accepts all valid enum values', () => { + const statusSchema = factory.makeEnumSchema('Status'); + expect(statusSchema.safeParse('ACTIVE').success).toBe(true); + expect(statusSchema.safeParse('INACTIVE').success).toBe(true); + expect(statusSchema.safeParse('PENDING').success).toBe(true); + }); + + it('rejects values not in the enum', () => { + const statusSchema = factory.makeEnumSchema('Status'); + expect(statusSchema.safeParse('ADMIN').success).toBe(false); + expect(statusSchema.safeParse('active').success).toBe(false); + expect(statusSchema.safeParse('').success).toBe(false); + expect(statusSchema.safeParse(null).success).toBe(false); + expect(statusSchema.safeParse(42).success).toBe(false); + }); + + it('infers enum value union type', () => { + const _statusSchema = factory.makeEnumSchema('Status'); + type Status = z.infer; + expectTypeOf().toEqualTypeOf<'ACTIVE' | 'INACTIVE' | 'PENDING'>(); + }); + + it('throws when enum is not found', () => { + expect(() => factory.makeEnumSchema('Unknown' as any)).toThrow(); + }); +}); diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts new file mode 100644 index 000000000..ae8c3be32 --- /dev/null +++ b/packages/zod/test/schema/schema.ts @@ -0,0 +1,186 @@ +////////////////////////////////////////////////////////////////////////////////////////////// +// DO NOT MODIFY THIS FILE // +// This file is automatically generated by ZenStack CLI and should not be manually updated. // +////////////////////////////////////////////////////////////////////////////////////////////// + +/* eslint-disable */ + +import { type SchemaDef, ExpressionUtils } from "@zenstackhq/schema"; +export class SchemaType implements SchemaDef { + provider = { + type: "sqlite" + } as const; + models = { + User: { + name: "User", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], + default: ExpressionUtils.call("cuid") + }, + email: { + name: "email", + type: "String", + attributes: [{ name: "@email" }] + }, + username: { + name: "username", + type: "String", + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(3) }, { name: "max", value: ExpressionUtils.literal(50) }] }] + }, + website: { + name: "website", + type: "String", + optional: true, + attributes: [{ name: "@url" }] + }, + code: { + name: "code", + type: "String", + attributes: [{ name: "@startsWith", args: [{ name: "text", value: ExpressionUtils.literal("USR") }] }] + }, + age: { + name: "age", + type: "Int", + attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }, { name: "@lte", args: [{ name: "value", value: ExpressionUtils.literal(150) }] }] + }, + score: { + name: "score", + type: "Float", + attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0.0) }] }, { name: "@lt", args: [{ name: "value", value: ExpressionUtils.literal(100.0) }] }] + }, + bigNum: { + name: "bigNum", + type: "BigInt", + attributes: [{ name: "@gte", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] + }, + balance: { + name: "balance", + type: "Decimal", + attributes: [{ name: "@gt", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }] + }, + active: { + name: "active", + type: "Boolean" + }, + birthdate: { + name: "birthdate", + type: "DateTime", + optional: true + }, + avatar: { + name: "avatar", + type: "Bytes", + optional: true + }, + metadata: { + name: "metadata", + type: "Json", + optional: true + }, + status: { + name: "status", + type: "Status" + }, + address: { + name: "address", + type: "Address", + optional: true, + attributes: [{ name: "@json" }] + }, + posts: { + name: "posts", + type: "Post", + array: true, + relation: { opposite: "author" } + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("age"), ">=", ExpressionUtils.literal(18)) }, { name: "message", value: ExpressionUtils.literal("Must be adult") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("age")]) }] } + ], + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + }, + Post: { + name: "Post", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], + default: ExpressionUtils.call("cuid") + }, + title: { + name: "title", + type: "String" + }, + published: { + name: "published", + type: "Boolean" + }, + author: { + name: "author", + type: "User", + optional: true, + attributes: [{ name: "@relation", args: [{ name: "fields", value: ExpressionUtils.array("String", [ExpressionUtils.field("authorId")]) }, { name: "references", value: ExpressionUtils.array("String", [ExpressionUtils.field("id")]) }] }], + relation: { opposite: "posts", fields: ["authorId"], references: ["id"] } + }, + authorId: { + name: "authorId", + type: "String", + optional: true, + foreignKeyFor: [ + "author" + ] + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + } + } + } as const; + typeDefs = { + Address: { + name: "Address", + fields: { + street: { + name: "street", + type: "String" + }, + city: { + name: "city", + type: "String", + attributes: [{ name: "@length", args: [{ name: "min", value: ExpressionUtils.literal(2) }] }] + }, + zip: { + name: "zip", + type: "String", + optional: true + } + }, + attributes: [ + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] } + ] + } + } as const; + enums = { + Status: { + name: "Status", + values: { + ACTIVE: "ACTIVE", + INACTIVE: "INACTIVE", + PENDING: "PENDING" + } + } + } as const; + authType = "User" as const; + plugins = {}; +} +export const schema = new SchemaType(); diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel new file mode 100644 index 000000000..fd651c927 --- /dev/null +++ b/packages/zod/test/schema/schema.zmodel @@ -0,0 +1,46 @@ +datasource db { + provider = 'sqlite' +} + +enum Status { + ACTIVE + INACTIVE + PENDING +} + +type Address { + street String + city String @length(2) + zip String? + + @@validate(zip == null || length(zip) == 5, "Zip code must be exactly 5 characters", ["zip"]) +} + +model User { + id String @id @default(cuid()) + email String @email + username String @length(3, 50) + website String? @url + code String @startsWith("USR") + age Int @gt(0) @lte(150) + score Float @gte(0.0) @lt(100.0) + bigNum BigInt @gte(0) + balance Decimal @gt(0) + active Boolean + birthdate DateTime? + avatar Bytes? + metadata Json? + status Status + address Address? @json + posts Post[] + + @@validate(age >= 18, "Must be adult", ["age"]) +} + +model Post { + id String @id @default(cuid()) + title String + published Boolean + author User? @relation(fields: [authorId], references: [id]) + authorId String? +} diff --git a/packages/zod/vitest.config.ts b/packages/zod/vitest.config.ts new file mode 100644 index 000000000..75a9f709c --- /dev/null +++ b/packages/zod/vitest.config.ts @@ -0,0 +1,4 @@ +import base from '@zenstackhq/vitest-config/base'; +import { defineConfig, mergeConfig } from 'vitest/config'; + +export default mergeConfig(base, defineConfig({})); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5828dbe57..b484975aa 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -525,6 +525,9 @@ importers: '@zenstackhq/schema': specifier: workspace:* version: link:../schema + '@zenstackhq/zod': + specifier: workspace:* + version: link:../zod better-sqlite3: specifier: 'catalog:' version: 12.5.0 @@ -642,6 +645,9 @@ importers: '@zenstackhq/typescript-config': specifier: workspace:* version: link:../config/typescript-config + '@zenstackhq/vitest-config': + specifier: workspace:* + version: link:../config/vitest-config packages/sdk: dependencies: @@ -828,9 +834,18 @@ importers: packages/zod: dependencies: - '@zenstackhq/orm': + '@zenstackhq/common-helpers': specifier: workspace:* - version: link:../orm + version: link:../common-helpers + '@zenstackhq/schema': + specifier: workspace:* + version: link:../schema + decimal.js: + specifier: 'catalog:' + version: 10.6.0 + json-stable-stringify: + specifier: ^1.3.0 + version: 1.3.0 ts-pattern: specifier: 'catalog:' version: 5.7.1 @@ -841,6 +856,9 @@ importers: '@zenstackhq/typescript-config': specifier: workspace:* version: link:../config/typescript-config + '@zenstackhq/vitest-config': + specifier: workspace:* + version: link:../config/vitest-config zod: specifier: ^4.1.0 version: 4.1.12 diff --git a/scripts/test-generate.ts b/scripts/test-generate.ts index e799ecf45..0af24290e 100644 --- a/scripts/test-generate.ts +++ b/scripts/test-generate.ts @@ -21,9 +21,12 @@ async function main() { async function generate(schemaPath: string, options: string[]) { const cliPath = path.join(_dirname, '../packages/cli/dist/index.js'); const RUNTIME = process.env.RUNTIME ?? 'node'; - execSync(`${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')}`, { - cwd: path.dirname(schemaPath), - }); + execSync( + `${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false`, + { + cwd: path.dirname(schemaPath), + }, + ); } main(); diff --git a/tests/e2e/orm/client-api/create-many.test.ts b/tests/e2e/orm/client-api/create-many.test.ts index c55f05ac4..af944f4f9 100644 --- a/tests/e2e/orm/client-api/create-many.test.ts +++ b/tests/e2e/orm/client-api/create-many.test.ts @@ -15,11 +15,6 @@ describe('Client createMany tests', () => { }); it('works with toplevel createMany', async () => { - // empty - await expect(client.user.createMany()).resolves.toMatchObject({ - count: 0, - }); - // single await expect( client.user.createMany({ diff --git a/tests/e2e/orm/client-api/zod.test-d.ts b/tests/e2e/orm/client-api/zod.test-d.ts new file mode 100644 index 000000000..148c47901 --- /dev/null +++ b/tests/e2e/orm/client-api/zod.test-d.ts @@ -0,0 +1,366 @@ +import { definePlugin, type ClientContract, type ClientOptions } from '@zenstackhq/orm'; +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expectTypeOf, it } from 'vitest'; +import z from 'zod'; +import { schema } from '../schemas/basic'; + +declare const client: ClientContract; + +describe('Zod schema typing tests', () => { + it('makeFindManySchema returns a typed schema', () => { + const s = client.$zod.makeFindManySchema('User'); + type Args = z.infer; + // all find args are optional + expectTypeOf>().toHaveProperty('where'); + expectTypeOf>().toHaveProperty('take'); + expectTypeOf>().toHaveProperty('skip'); + expectTypeOf>().toHaveProperty('orderBy'); + expectTypeOf>().toHaveProperty('select'); + expectTypeOf>().toHaveProperty('include'); + expectTypeOf>().toHaveProperty('cursor'); + }); + + it('makeFindUniqueSchema returns a typed schema', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + type Args = z.infer; + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('include'); + // where has id and email (unique fields) + expectTypeOf().toHaveProperty('id'); + expectTypeOf().toHaveProperty('email'); + }); + + it('makeFindFirstSchema returns a typed schema', () => { + const s = client.$zod.makeFindFirstSchema('User'); + type Args = z.infer; + expectTypeOf>().toHaveProperty('where'); + expectTypeOf>().toHaveProperty('take'); + }); + + it('makeExistsSchema returns a typed schema', () => { + const s = client.$zod.makeExistsSchema('User'); + type Args = NonNullable>; + expectTypeOf().toHaveProperty('where'); + expectTypeOf().not.toHaveProperty('select'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeCreateSchema returns a typed schema', () => { + const s = client.$zod.makeCreateSchema('User'); + type Args = z.infer; + // data is required + expectTypeOf().toHaveProperty('data'); + // select / include / omit are optional + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('include'); + expectTypeOf().toHaveProperty('omit'); + // data has required field email and optional field name + expectTypeOf().toHaveProperty('email'); + expectTypeOf().toHaveProperty('name'); + }); + + it('makeCreateManySchema returns a typed schema', () => { + const s = client.$zod.makeCreateManySchema('User'); + type Args = z.infer; + // data is required + expectTypeOf().toHaveProperty('data'); + // skipDuplicates is optional + expectTypeOf().toHaveProperty('skipDuplicates'); + // no select / include on createMany + expectTypeOf().not.toHaveProperty('select'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeCreateManyAndReturnSchema returns a typed schema', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + type Args = NonNullable>; + // data and skipDuplicates from createMany payload + expectTypeOf().toHaveProperty('data'); + expectTypeOf().toHaveProperty('skipDuplicates'); + // select and omit are supported; include is not + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('omit'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeUpdateSchema returns a typed schema', () => { + const s = client.$zod.makeUpdateSchema('User'); + type Args = z.infer; + // where (unique) and data are required + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('data'); + // select / include / omit are present + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('include'); + expectTypeOf().toHaveProperty('omit'); + // where is limited to unique fields (id and email) + expectTypeOf().toHaveProperty('id'); + expectTypeOf().toHaveProperty('email'); + // data has updatable fields + expectTypeOf().toHaveProperty('name'); + expectTypeOf().toHaveProperty('role'); + }); + + it('makeUpdateManySchema returns a typed schema', () => { + const s = client.$zod.makeUpdateManySchema('User'); + type Args = z.infer; + // data is required; where and limit are optional + expectTypeOf().toHaveProperty('data'); + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('limit'); + // no select / include on updateMany + expectTypeOf().not.toHaveProperty('select'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeUpdateManyAndReturnSchema returns a typed schema', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + type Args = z.infer; + // data is required; where and limit are optional + expectTypeOf().toHaveProperty('data'); + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('limit'); + // select and omit are present; include is not + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('omit'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeUpsertSchema returns a typed schema', () => { + const s = client.$zod.makeUpsertSchema('User'); + type Args = z.infer; + // where (unique), create, and update are all required + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('create'); + expectTypeOf().toHaveProperty('update'); + // select / include / omit are present + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('include'); + expectTypeOf().toHaveProperty('omit'); + // create has the required email field; update has optional fields + expectTypeOf().toHaveProperty('email'); + expectTypeOf().toHaveProperty('name'); + }); + + it('makeDeleteSchema returns a typed schema', () => { + const s = client.$zod.makeDeleteSchema('User'); + type Args = z.infer; + // where (unique) is required; no data field + expectTypeOf().toHaveProperty('where'); + expectTypeOf().not.toHaveProperty('data'); + // select / include / omit are present + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('include'); + expectTypeOf().toHaveProperty('omit'); + // where is limited to unique fields (id and email) + expectTypeOf().toHaveProperty('id'); + expectTypeOf().toHaveProperty('email'); + }); + + it('makeDeleteManySchema returns a typed schema', () => { + const s = client.$zod.makeDeleteManySchema('User'); + type Args = NonNullable>; + // where and limit are optional; no data field + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('limit'); + expectTypeOf().not.toHaveProperty('data'); + // no select / include on deleteMany + expectTypeOf().not.toHaveProperty('select'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeCountSchema returns a typed schema', () => { + const s = client.$zod.makeCountSchema('User'); + type Args = NonNullable>; + // where, select, skip, take, orderBy are present + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('select'); + expectTypeOf().toHaveProperty('skip'); + expectTypeOf().toHaveProperty('take'); + expectTypeOf().toHaveProperty('orderBy'); + // no data, include, omit + expectTypeOf().not.toHaveProperty('data'); + expectTypeOf().not.toHaveProperty('include'); + }); + + it('makeAggregateSchema returns a typed schema', () => { + const s = client.$zod.makeAggregateSchema('User'); + type Args = NonNullable>; + // standard query args + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('skip'); + expectTypeOf().toHaveProperty('take'); + expectTypeOf().toHaveProperty('orderBy'); + // aggregation operators + expectTypeOf().toHaveProperty('_count'); + expectTypeOf().toHaveProperty('_avg'); + expectTypeOf().toHaveProperty('_sum'); + expectTypeOf().toHaveProperty('_min'); + expectTypeOf().toHaveProperty('_max'); + }); + + it('makeGroupBySchema returns a typed schema', () => { + const s = client.$zod.makeGroupBySchema('User'); + type Args = z.infer; + // by is required; where, orderBy, having, skip, take, aggregations are optional + expectTypeOf().toHaveProperty('by'); + expectTypeOf().toHaveProperty('where'); + expectTypeOf().toHaveProperty('orderBy'); + expectTypeOf().toHaveProperty('having'); + expectTypeOf().toHaveProperty('skip'); + expectTypeOf().toHaveProperty('take'); + expectTypeOf().toHaveProperty('_count'); + }); +}); + +describe('Zod schema with slicing - typing', () => { + it('model exclusion removes relation field from include type', async () => { + type ExcludePostOptions = ClientOptions & { + slicing: { excludedModels: readonly ['Post'] }; + }; + const slicingClient = await createTestClient(schema, { + slicing: { excludedModels: ['Post'] as const }, + }); + const s = slicingClient.$zod.makeFindManySchema('User'); + type Include = NonNullable>['include']>; + // 'posts' relation is excluded → not in include type + expectTypeOf().not.toHaveProperty('posts'); + // 'profile' is not excluded → still in include type + expectTypeOf().toHaveProperty('profile'); + }); + + it('includedModels restricts relation fields in include type', async () => { + type IncludeUserProfileOptions = ClientOptions & { + slicing: { includedModels: readonly ['User', 'Profile'] }; + }; + const slicingClient = await createTestClient(schema, { + slicing: { includedModels: ['User', 'Profile'] as const }, + }); + const s = slicingClient.$zod.makeFindManySchema('User'); + type Include = NonNullable>['include']>; + // 'profile' points to Profile which is included + expectTypeOf().toHaveProperty('profile'); + // 'posts' points to Post which is NOT in includedModels → not in include type + expectTypeOf().not.toHaveProperty('posts'); + }); + + it('includedFilterKinds: Equality removes Range operators from number filter type', async () => { + type EqualityOnlyOptions = ClientOptions & { + slicing: { + models: { + user: { fields: { $all: { includedFilterKinds: readonly ['Equality'] } } }; + }; + }; + }; + const slicingClient = await createTestClient(schema, { + slicing: { + models: { user: { fields: { $all: { includedFilterKinds: ['Equality'] as const } } } }, + }, + }); + const s = slicingClient.$zod.makeFindManySchema('User'); + type Where = NonNullable>['where']>; + // Range operators are excluded → type error + // @ts-expect-error 'gt' is not a valid operator when only Equality is included + const _rangeInvalid: Where = { age: { gt: 25 } }; + void _rangeInvalid; + // Equality operators are still valid + const _equalityValid: Where = { age: { equals: 25 } }; + void _equalityValid; + }); + + it('includedFilterKinds: Equality removes Like operators from string filter type', async () => { + type EqualityOnlyOptions = ClientOptions & { + slicing: { + models: { + user: { fields: { $all: { includedFilterKinds: readonly ['Equality'] } } }; + }; + }; + }; + const slicingClient = await createTestClient(schema, { + slicing: { + models: { user: { fields: { $all: { includedFilterKinds: ['Equality'] as const } } } }, + }, + }); + const s = slicingClient.$zod.makeFindManySchema('User'); + type Where = NonNullable>['where']>; + // Like operators are excluded → type error + // @ts-expect-error 'contains' is not a valid operator when only Equality is included + const _likeInvalid: Where = { email: { contains: 'test' } }; + void _likeInvalid; + // Equality operators are still valid + const _equalityValid: Where = { email: { equals: 'test@example.com' } }; + void _equalityValid; + }); + + it('excludedFilterKinds: Range removes range operators while keeping equality and like', async () => { + type ExcludeRangeOptions = ClientOptions & { + slicing: { + models: { + user: { fields: { $all: { excludedFilterKinds: readonly ['Range'] } } }; + }; + }; + }; + const slicingClient = await createTestClient(schema, { + slicing: { + models: { user: { fields: { $all: { excludedFilterKinds: ['Range'] as const } } } }, + }, + }); + const s = slicingClient.$zod.makeFindManySchema('User'); + type Where = NonNullable>['where']>; + // Range operators are excluded → type error + // @ts-expect-error 'gt' is not a valid operator when Range is excluded + const _rangeInvalid: Where = { age: { gt: 25 } }; + void _rangeInvalid; + // Equality operators are still valid + const _equalityValid: Where = { age: { equals: 25 } }; + void _equalityValid; + // Like operators on string fields are still valid + const _likeValid: Where = { email: { contains: 'test' } }; + void _likeValid; + }); +}); + +describe('Zod schema with plugins - query args extension typing', () => { + const cachePlugin = definePlugin({ + id: 'cache', + queryArgs: { + $read: z.object({ cache: z.object({ ttl: z.number().optional() }).optional() }), + $create: z.object({ cache: z.object({ bust: z.boolean().optional() }).optional() }), + }, + }); + + it('find schema includes extended read args in type', () => { + const extClient = client.$use(cachePlugin); + const s = extClient.$zod.makeFindManySchema('User'); + type Args = NonNullable>; + expectTypeOf().toHaveProperty('cache'); + expectTypeOf>().toHaveProperty('ttl'); + }); + + it('create schema includes create-specific extended args in type', () => { + const extClient = client.$use(cachePlugin); + const s = extClient.$zod.makeCreateSchema('User'); + type Args = z.infer; + expectTypeOf().toHaveProperty('cache'); + // @ts-expect-error 'ttl' belongs to $read args, not $create args + const _invalid: Args = { data: { email: 'u@test.com' }, cache: { ttl: 1000 } }; + void _invalid; + }); + + it('$all extended args appear in all schema types', () => { + const sourcePlugin = definePlugin({ + id: 'source', + queryArgs: { + $all: z.object({ source: z.string().optional() }), + }, + }); + const extClient = client.$use(sourcePlugin); + const findSchema = extClient.$zod.makeFindManySchema('User'); + type FindArgs = NonNullable>; + expectTypeOf().toHaveProperty('source'); + const createSchema = extClient.$zod.makeCreateSchema('User'); + type CreateArgs = z.infer; + expectTypeOf().toHaveProperty('source'); + }); +}); diff --git a/tests/e2e/orm/client-api/zod.test.ts b/tests/e2e/orm/client-api/zod.test.ts new file mode 100644 index 000000000..377b68743 --- /dev/null +++ b/tests/e2e/orm/client-api/zod.test.ts @@ -0,0 +1,1063 @@ +import { createQuerySchemaFactory, definePlugin, type ClientContract } from '@zenstackhq/orm'; +import { createTestClient, getTestDbProvider } from '@zenstackhq/testtools'; +import { afterEach, beforeEach, describe as _describe, expect, it } from 'vitest'; +import z from 'zod'; +import { schema } from '../schemas/basic'; + +// only run for sqlite because schemas are provider independent +const describe = getTestDbProvider() === 'sqlite' ? _describe : _describe.skip; + +describe('Zod schema factory test', () => { + if (getTestDbProvider() !== 'sqlite') { + return; + } + + let client: ClientContract; + + beforeEach(async () => { + client = await createTestClient(schema); + }); + + afterEach(async () => { + await client?.$disconnect(); + }); + + describe('CRUD schemas tests', () => { + // #region Find + + describe('makeFindManySchema', () => { + it('accepts undefined (all args optional)', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts valid where clause', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { email: { equals: 'u@test.com' } } }).success).toBe(true); + }); + + it('accepts string filter operators', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { email: { contains: 'test', startsWith: 'u' } } }).success).toBe(true); + }); + + it('accepts number filter operators', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { age: { gt: 18, lte: 65 } } }).success).toBe(true); + }); + + it('accepts enum filter', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { role: { in: ['USER', 'ADMIN'] } } }).success).toBe(true); + }); + + it('accepts logical combinators (AND/OR/NOT)', () => { + const s = client.$zod.makeFindManySchema('User'); + expect( + s.safeParse({ + where: { + AND: [{ email: { contains: 'test' } }, { role: 'USER' }], + }, + }).success, + ).toBe(true); + expect(s.safeParse({ where: { NOT: { email: { equals: 'admin@test.com' } } } }).success).toBe(true); + }); + + it('accepts relation filter', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(true); + }); + + it('accepts pagination args (take, skip)', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ take: 10, skip: 20 }).success).toBe(true); + }); + + it('accepts orderBy', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ orderBy: { email: 'asc' } }).success).toBe(true); + expect(s.safeParse({ orderBy: [{ email: 'asc' }, { name: 'desc' }] }).success).toBe(true); + }); + + it('accepts select', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ select: { id: true, email: true } }).success).toBe(true); + }); + + it('accepts include', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ include: { posts: true, profile: true } }).success).toBe(true); + }); + + it('rejects select and include together', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ select: { id: true }, include: { posts: true } }).success).toBe(false); + }); + + it('rejects select and omit together', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ select: { id: true }, omit: { name: true } }).success).toBe(false); + }); + + it('rejects unknown where field', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + + it('rejects invalid enum value in where', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ where: { role: 'SUPERUSER' } }).success).toBe(false); + }); + + it('rejects non-integer take', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ take: 1.5 }).success).toBe(false); + }); + + it('accepts negative take (cursor-based pagination)', () => { + const s = client.$zod.makeFindManySchema('User'); + // negative take is valid: means "take the last N results" + expect(s.safeParse({ take: -1 }).success).toBe(true); + }); + }); + + describe('makeFindUniqueSchema', () => { + it('accepts where with unique id field', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + expect(s.safeParse({ where: { id: 'u1' } }).success).toBe(true); + }); + + it('accepts where with unique email field', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + }); + + it('accepts optional select/include', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + expect(s.safeParse({ where: { id: 'u1' }, include: { posts: true } }).success).toBe(true); + }); + + it('rejects empty where', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + expect(s.safeParse({ where: {} }).success).toBe(false); + }); + + it('rejects non-unique where field', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + // name is not a unique field + expect(s.safeParse({ where: { name: 'Alice' } }).success).toBe(false); + }); + + it('rejects missing where', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + expect(s.safeParse({}).success).toBe(false); + }); + + it('rejects select and include together', () => { + const s = client.$zod.makeFindUniqueSchema('User'); + expect( + s.safeParse({ where: { id: 'u1' }, select: { id: true }, include: { posts: true } }).success, + ).toBe(false); + }); + }); + + describe('makeFindFirstSchema', () => { + it('accepts undefined (all args optional)', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts valid where clause', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ where: { email: { contains: 'test' } } }).success).toBe(true); + }); + + it('accepts non-unique where field (unlike findUnique)', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ where: { name: 'Alice' } }).success).toBe(true); + }); + + it('accepts pagination args (take, skip)', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ take: 1, skip: 5 }).success).toBe(true); + }); + + it('accepts orderBy', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ orderBy: { email: 'asc' } }).success).toBe(true); + }); + + it('accepts select', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ select: { id: true, email: true } }).success).toBe(true); + }); + + it('accepts include', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ include: { posts: true } }).success).toBe(true); + }); + + it('rejects select and include together', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ select: { id: true }, include: { posts: true } }).success).toBe(false); + }); + + it('rejects unknown where field', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + + it('rejects invalid enum value in where', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ where: { role: 'SUPERUSER' } }).success).toBe(false); + }); + + it('rejects non-integer take', () => { + const s = client.$zod.makeFindFirstSchema('User'); + expect(s.safeParse({ take: 1.5 }).success).toBe(false); + }); + }); + + describe('makeExistsSchema', () => { + it('accepts undefined (all optional)', () => { + const s = client.$zod.makeExistsSchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts empty object (where is optional)', () => { + const s = client.$zod.makeExistsSchema('User'); + expect(s.safeParse({}).success).toBe(true); + }); + + it('accepts where clause', () => { + const s = client.$zod.makeExistsSchema('User'); + expect(s.safeParse({ where: { role: 'USER' } }).success).toBe(true); + }); + + it('rejects unknown where field', () => { + const s = client.$zod.makeExistsSchema('User'); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + }); + + // #endregion + + // #region Create + + describe('makeCreateSchema', () => { + it('accepts minimal valid create input (required fields only)', () => { + const s = client.$zod.makeCreateSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' } }).success).toBe(true); + }); + + it('accepts full create input with optional fields', () => { + const s = client.$zod.makeCreateSchema('User'); + expect( + s.safeParse({ + data: { email: 'u@test.com', name: 'Alice', age: 30, role: 'ADMIN' }, + }).success, + ).toBe(true); + }); + + it('accepts nested relation in create (nested create)', () => { + const s = client.$zod.makeCreateSchema('User'); + expect( + s.safeParse({ + data: { + email: 'u@test.com', + posts: { create: { title: 'Hello' } }, + }, + }).success, + ).toBe(true); + }); + + it('accepts select/include in create args', () => { + const s = client.$zod.makeCreateSchema('User'); + expect( + s.safeParse({ + data: { email: 'u@test.com' }, + select: { id: true, email: true }, + }).success, + ).toBe(true); + }); + + it('rejects missing required field (email)', () => { + const s = client.$zod.makeCreateSchema('User'); + expect(s.safeParse({ data: {} }).success).toBe(false); + }); + + it('rejects invalid enum value', () => { + const s = client.$zod.makeCreateSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com', role: 'SUPERUSER' } }).success).toBe(false); + }); + + it('rejects unknown field in data', () => { + const s = client.$zod.makeCreateSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com', notAField: 'val' } }).success).toBe(false); + }); + + it('rejects missing data wrapper', () => { + const s = client.$zod.makeCreateSchema('User'); + expect(s.safeParse({ email: 'u@test.com' }).success).toBe(false); + }); + + it('rejects select and include together', () => { + const s = client.$zod.makeCreateSchema('User'); + expect( + s.safeParse({ data: { email: 'u@test.com' }, select: { id: true }, include: { posts: true } }) + .success, + ).toBe(false); + }); + }); + + describe('makeCreateManySchema', () => { + it('accepts single record as data', () => { + const s = client.$zod.makeCreateManySchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' } }).success).toBe(true); + }); + + it('accepts array of records as data', () => { + const s = client.$zod.makeCreateManySchema('User'); + expect(s.safeParse({ data: [{ email: 'a@test.com' }, { email: 'b@test.com' }] }).success).toBe(true); + }); + + it('accepts skipDuplicates flag', () => { + const s = client.$zod.makeCreateManySchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' }, skipDuplicates: true }).success).toBe(true); + }); + + it('rejects missing data', () => { + const s = client.$zod.makeCreateManySchema('User'); + expect(s.safeParse({}).success).toBe(false); + }); + + it('rejects unknown field in data', () => { + const s = client.$zod.makeCreateManySchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com', notAField: 'val' } }).success).toBe(false); + }); + + it('rejects invalid enum value in data', () => { + const s = client.$zod.makeCreateManySchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com', role: 'SUPERUSER' } }).success).toBe(false); + }); + }); + + describe('makeCreateManyAndReturnSchema', () => { + it('accepts undefined (whole schema is optional)', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts single record as data', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' } }).success).toBe(true); + }); + + it('accepts array of records as data', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse({ data: [{ email: 'a@test.com' }, { email: 'b@test.com' }] }).success).toBe(true); + }); + + it('accepts skipDuplicates flag', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' }, skipDuplicates: true }).success).toBe(true); + }); + + it('accepts select', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' }, select: { id: true } }).success).toBe(true); + }); + + it('accepts omit', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' }, omit: { name: true } }).success).toBe(true); + }); + + it('rejects select and omit together', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect( + s.safeParse({ data: { email: 'u@test.com' }, select: { id: true }, omit: { name: true } }).success, + ).toBe(false); + }); + + it('rejects unknown field in data', () => { + const s = client.$zod.makeCreateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com', notAField: 'val' } }).success).toBe(false); + }); + }); + + // #endregion + + // #region Update + + describe('makeUpdateSchema', () => { + it('accepts valid update args', () => { + const s = client.$zod.makeUpdateSchema('User'); + expect(s.safeParse({ where: { id: 'u1' }, data: { name: 'Alice' } }).success).toBe(true); + }); + + it('accepts update with enum field', () => { + const s = client.$zod.makeUpdateSchema('User'); + expect(s.safeParse({ where: { id: 'u1' }, data: { role: 'ADMIN' } }).success).toBe(true); + }); + + it('accepts update with nested relation', () => { + const s = client.$zod.makeUpdateSchema('User'); + expect( + s.safeParse({ + where: { id: 'u1' }, + data: { posts: { create: { title: 'New Post' } } }, + }).success, + ).toBe(true); + }); + + it('rejects missing where', () => { + const s = client.$zod.makeUpdateSchema('User'); + expect(s.safeParse({ data: { name: 'Alice' } }).success).toBe(false); + }); + + it('rejects missing data', () => { + const s = client.$zod.makeUpdateSchema('User'); + expect(s.safeParse({ where: { id: 'u1' } }).success).toBe(false); + }); + + it('rejects non-unique where', () => { + const s = client.$zod.makeUpdateSchema('User'); + // name is not a unique field + expect(s.safeParse({ where: { name: 'Alice' }, data: { name: 'Bob' } }).success).toBe(false); + }); + + it('rejects invalid enum value in data', () => { + const s = client.$zod.makeUpdateSchema('User'); + expect(s.safeParse({ where: { id: 'u1' }, data: { role: 'INVALID' } }).success).toBe(false); + }); + }); + + describe('makeUpdateManySchema', () => { + it('accepts valid updateMany (where is optional)', () => { + const s = client.$zod.makeUpdateManySchema('User'); + expect(s.safeParse({ data: { name: 'Updated' } }).success).toBe(true); + }); + + it('accepts updateMany with non-unique where', () => { + const s = client.$zod.makeUpdateManySchema('User'); + expect(s.safeParse({ where: { role: 'USER' }, data: { name: 'Updated' } }).success).toBe(true); + }); + + it('rejects missing data', () => { + const s = client.$zod.makeUpdateManySchema('User'); + expect(s.safeParse({ where: { role: 'USER' } }).success).toBe(false); + }); + }); + + describe('makeUpdateManyAndReturnSchema', () => { + it('accepts minimal valid args (data required)', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { name: 'Updated' } }).success).toBe(true); + }); + + it('accepts where clause', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect(s.safeParse({ where: { role: 'USER' }, data: { name: 'Updated' } }).success).toBe(true); + }); + + it('accepts select', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { name: 'Updated' }, select: { id: true } }).success).toBe(true); + }); + + it('accepts omit', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { name: 'Updated' }, omit: { name: true } }).success).toBe(true); + }); + + it('rejects select and omit together', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect( + s.safeParse({ data: { name: 'Updated' }, select: { id: true }, omit: { name: true } }).success, + ).toBe(false); + }); + + it('rejects missing data', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect(s.safeParse({ where: { role: 'USER' } }).success).toBe(false); + }); + + it('rejects invalid enum in data', () => { + const s = client.$zod.makeUpdateManyAndReturnSchema('User'); + expect(s.safeParse({ data: { role: 'INVALID' } }).success).toBe(false); + }); + }); + + describe('makeUpsertSchema', () => { + it('accepts valid upsert args', () => { + const s = client.$zod.makeUpsertSchema('User'); + expect( + s.safeParse({ + where: { id: 'u1' }, + create: { email: 'u@test.com' }, + update: { name: 'Alice' }, + }).success, + ).toBe(true); + }); + + it('rejects missing create', () => { + const s = client.$zod.makeUpsertSchema('User'); + expect(s.safeParse({ where: { id: 'u1' }, update: { name: 'Alice' } }).success).toBe(false); + }); + + it('rejects missing update', () => { + const s = client.$zod.makeUpsertSchema('User'); + expect(s.safeParse({ where: { id: 'u1' }, create: { email: 'u@test.com' } }).success).toBe(false); + }); + + it('rejects missing where', () => { + const s = client.$zod.makeUpsertSchema('User'); + expect(s.safeParse({ create: { email: 'u@test.com' }, update: { name: 'Alice' } }).success).toBe(false); + }); + + it('rejects invalid enum in create', () => { + const s = client.$zod.makeUpsertSchema('User'); + expect( + s.safeParse({ + where: { id: 'u1' }, + create: { email: 'u@test.com', role: 'BAD' }, + update: {}, + }).success, + ).toBe(false); + }); + }); + + // #endregion + + // #region Delete + + describe('makeDeleteSchema', () => { + it('accepts valid delete args with unique where', () => { + const s = client.$zod.makeDeleteSchema('User'); + expect(s.safeParse({ where: { id: 'u1' } }).success).toBe(true); + }); + + it('accepts unique email in where', () => { + const s = client.$zod.makeDeleteSchema('User'); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + }); + + it('rejects missing where', () => { + const s = client.$zod.makeDeleteSchema('User'); + expect(s.safeParse({}).success).toBe(false); + }); + + it('rejects empty where', () => { + const s = client.$zod.makeDeleteSchema('User'); + expect(s.safeParse({ where: {} }).success).toBe(false); + }); + + it('rejects non-unique where field', () => { + const s = client.$zod.makeDeleteSchema('User'); + expect(s.safeParse({ where: { name: 'Alice' } }).success).toBe(false); + }); + }); + + describe('makeDeleteManySchema', () => { + it('accepts undefined (where optional, deletes all)', () => { + const s = client.$zod.makeDeleteManySchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts non-unique where', () => { + const s = client.$zod.makeDeleteManySchema('User'); + expect(s.safeParse({ where: { role: 'USER' } }).success).toBe(true); + }); + + it('rejects unknown where field', () => { + const s = client.$zod.makeDeleteManySchema('User'); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + }); + + // #endregion + + // #region Aggregation + + describe('makeCountSchema', () => { + it('accepts undefined (all optional)', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts where clause', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse({ where: { role: 'USER' } }).success).toBe(true); + }); + + it('accepts select: true (count all)', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse({ select: true }).success).toBe(true); + }); + + it('accepts select with field names', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse({ select: { id: true, email: true } }).success).toBe(true); + }); + + it('accepts take and skip', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse({ take: 10, skip: 5 }).success).toBe(true); + }); + + it('accepts orderBy', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse({ orderBy: { email: 'asc' } }).success).toBe(true); + }); + + it('rejects unknown where field', () => { + const s = client.$zod.makeCountSchema('User'); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + }); + + describe('makeAggregateSchema', () => { + it('accepts undefined (all optional)', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse(undefined).success).toBe(true); + }); + + it('accepts where clause', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ where: { role: 'USER' } }).success).toBe(true); + }); + + it('accepts _count: true', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ _count: true }).success).toBe(true); + }); + + it('accepts _count with field selection', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ _count: { id: true, email: true } }).success).toBe(true); + }); + + it('accepts _avg on numeric fields', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ _avg: { age: true } }).success).toBe(true); + }); + + it('accepts _sum on numeric fields', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ _sum: { age: true } }).success).toBe(true); + }); + + it('accepts _min and _max on non-array non-relation fields', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ _min: { age: true, email: true }, _max: { age: true } }).success).toBe(true); + }); + + it('accepts take and skip', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ take: 10, skip: 5 }).success).toBe(true); + }); + + it('accepts orderBy', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ orderBy: { age: 'asc' } }).success).toBe(true); + }); + + it('rejects unknown where field', () => { + const s = client.$zod.makeAggregateSchema('User'); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + }); + + describe('makeGroupBySchema', () => { + it('accepts single field in by', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role' }).success).toBe(true); + }); + + it('accepts multiple fields in by as array', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: ['role', 'name'] }).success).toBe(true); + }); + + it('rejects missing by', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({}).success).toBe(false); + }); + + it('rejects relation field in by', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'posts' }).success).toBe(false); + }); + + it('accepts where clause', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role', where: { role: 'USER' } }).success).toBe(true); + }); + + it('accepts _count aggregation', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role', _count: true }).success).toBe(true); + }); + + it('accepts _avg on numeric fields', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role', _avg: { age: true } }).success).toBe(true); + }); + + it('accepts orderBy matching the by field', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role', orderBy: { role: 'asc' } }).success).toBe(true); + }); + + it('rejects orderBy with a field not in by (without aggregation)', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role', orderBy: { name: 'asc' } }).success).toBe(false); + }); + + it('accepts take and skip', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'role', take: 10, skip: 0 }).success).toBe(true); + }); + + it('rejects unknown field in by', () => { + const s = client.$zod.makeGroupBySchema('User'); + expect(s.safeParse({ by: 'notAField' }).success).toBe(false); + }); + }); + + // #endregion + }); + + // #region Slicing + + describe('slicing - model exclusion', () => { + it('excluded model relation is rejected in select', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { excludedModels: ['Post'] }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + // 'posts' relation is excluded → rejected by strict schema + expect(s.safeParse({ select: { posts: true } }).success).toBe(false); + // scalar fields are unaffected + expect(s.safeParse({ select: { id: true, email: true } }).success).toBe(true); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('excluded model relation is rejected in include', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { excludedModels: ['Post'] }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + expect(s.safeParse({ include: { posts: true } }).success).toBe(false); + // 'profile' is not excluded, still allowed + expect(s.safeParse({ include: { profile: true } }).success).toBe(true); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('excluded model relation is rejected in create data', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { excludedModels: ['Post'] }, + }); + try { + const s = slicingClient.$zod.makeCreateSchema('User'); + expect( + s.safeParse({ + data: { email: 'u@test.com', posts: { create: { title: 'Hello' } } }, + }).success, + ).toBe(false); + // without the excluded relation, create still works + expect(s.safeParse({ data: { email: 'u@test.com' } }).success).toBe(true); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('excluded model relation is rejected in update data', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { excludedModels: ['Post'] }, + }); + try { + const s = slicingClient.$zod.makeUpdateSchema('User'); + expect( + s.safeParse({ + where: { id: 'u1' }, + data: { posts: { create: { title: 'New Post' } } }, + }).success, + ).toBe(false); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('includedModels restricts relations to allowed models only', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { includedModels: ['User', 'Profile'] }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + // Post is not included → posts relation rejected + expect(s.safeParse({ include: { posts: true } }).success).toBe(false); + // Profile is included → profile relation accepted + expect(s.safeParse({ include: { profile: true } }).success).toBe(true); + } finally { + await slicingClient.$disconnect(); + } + }); + }); + + describe('slicing - filter kinds', () => { + it('includedFilterKinds restricts to equality operators only', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { + models: { + user: { fields: { $all: { includedFilterKinds: ['Equality'] as const } } }, + }, + }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + // equality operators accepted + expect(s.safeParse({ where: { age: { equals: 25 } } }).success).toBe(true); + expect(s.safeParse({ where: { email: { in: ['a@b.com'] } } }).success).toBe(true); + // direct value still accepted (equality) + expect(s.safeParse({ where: { age: 25 } }).success).toBe(true); + // range operators rejected + expect(s.safeParse({ where: { age: { gt: 18 } } }).success).toBe(false); + expect(s.safeParse({ where: { age: { lte: 65 } } }).success).toBe(false); + expect(s.safeParse({ where: { age: { between: [10, 50] } } }).success).toBe(false); + // like operators rejected + expect(s.safeParse({ where: { email: { contains: 'test' } } }).success).toBe(false); + expect(s.safeParse({ where: { email: { startsWith: 'u' } } }).success).toBe(false); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('excludedFilterKinds removes specified operators while keeping others', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { + models: { + user: { fields: { $all: { excludedFilterKinds: ['Range'] as const } } }, + }, + }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + // equality operators still work + expect(s.safeParse({ where: { age: { equals: 25 } } }).success).toBe(true); + // like operators still work for string fields + expect(s.safeParse({ where: { email: { contains: 'test' } } }).success).toBe(true); + // direct value still works + expect(s.safeParse({ where: { age: 25 } }).success).toBe(true); + // range operators rejected + expect(s.safeParse({ where: { age: { gt: 18 } } }).success).toBe(false); + expect(s.safeParse({ where: { age: { lte: 65 } } }).success).toBe(false); + expect(s.safeParse({ where: { age: { between: [10, 50] } } }).success).toBe(false); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('field-level filter overrides model-level $all', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { + models: { + user: { + fields: { + $all: { includedFilterKinds: ['Equality'] as const }, + // 'name' additionally allows Like operators + name: { includedFilterKinds: ['Equality', 'Like'] as const }, + }, + }, + }, + }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + // 'name' has field-level override: allows Like + expect(s.safeParse({ where: { name: { contains: 'Alice' } } }).success).toBe(true); + expect(s.safeParse({ where: { name: { startsWith: 'A' } } }).success).toBe(true); + // 'email' falls back to $all: Equality only + expect(s.safeParse({ where: { email: { contains: 'test' } } }).success).toBe(false); + expect(s.safeParse({ where: { email: { equals: 'a@b.com' } } }).success).toBe(true); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('$all models fallback applies filter restrictions across all models', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { + models: { + $all: { fields: { $all: { includedFilterKinds: ['Equality'] as const } } }, + }, + }, + }); + try { + const userSchema = slicingClient.$zod.makeFindManySchema('User'); + const postSchema = slicingClient.$zod.makeFindManySchema('Post'); + // equality works for both models + expect(userSchema.safeParse({ where: { email: { equals: 'u@test.com' } } }).success).toBe(true); + expect(postSchema.safeParse({ where: { published: { equals: true } } }).success).toBe(true); + // range/like rejected for both models + expect(userSchema.safeParse({ where: { age: { gt: 18 } } }).success).toBe(false); + expect(postSchema.safeParse({ where: { title: { contains: 'hello' } } }).success).toBe(false); + } finally { + await slicingClient.$disconnect(); + } + }); + + it('Relation filter kind exclusion rejects relation-style filters on a field', async () => { + const slicingClient = await createTestClient(schema, { + slicing: { + models: { + user: { + fields: { + posts: { excludedFilterKinds: ['Relation'] as const }, + }, + }, + }, + }, + }); + try { + const s = slicingClient.$zod.makeFindManySchema('User'); + // relation-style filters on 'posts' are excluded + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + expect(s.safeParse({ where: { posts: { every: { published: true } } } }).success).toBe(false); + // scalar fields on the same model are unaffected + expect(s.safeParse({ where: { email: { equals: 'u@test.com' } } }).success).toBe(true); + } finally { + await slicingClient.$disconnect(); + } + }); + }); + + // #endregion + + // #region Plugin query args + + describe('plugin - query args extension', () => { + const cachePlugin = definePlugin({ + id: 'cache', + queryArgs: { + $read: z.object({ cache: z.strictObject({ ttl: z.number().min(0).optional() }).optional() }), + $create: z.object({ cache: z.strictObject({ bust: z.boolean().optional() }).optional() }), + }, + }); + + it('extended read args are accepted by find schema', () => { + const extClient = client.$use(cachePlugin); + const s = extClient.$zod.makeFindManySchema('User'); + expect(s.safeParse({ cache: { ttl: 1000 } }).success).toBe(true); + }); + + it('extended read args are validated (min constraint)', () => { + const extClient = client.$use(cachePlugin); + const s = extClient.$zod.makeFindManySchema('User'); + expect(s.safeParse({ cache: { ttl: -1 } }).success).toBe(false); + }); + + it('strict validation rejects unknown plugin arg keys', () => { + const extClient = client.$use(cachePlugin); + const s = extClient.$zod.makeFindManySchema('User'); + expect(s.safeParse({ cache: { ttl: 100, unknown: true } }).success).toBe(false); + }); + + it('create-specific extended args are accepted by create schema', () => { + const extClient = client.$use(cachePlugin); + const s = extClient.$zod.makeCreateSchema('User'); + expect(s.safeParse({ data: { email: 'u@test.com' }, cache: { bust: true } }).success).toBe(true); + }); + + it('base client schema rejects extended args', () => { + const s = client.$zod.makeFindManySchema('User'); + expect(s.safeParse({ cache: { ttl: 1000 } }).success).toBe(false); + }); + + it('$all extended args appear in all operation schemas', () => { + const sourcePlugin = definePlugin({ + id: 'source', + queryArgs: { + $all: z.object({ source: z.string().optional() }), + }, + }); + const extClient = client.$use(sourcePlugin); + expect(extClient.$zod.makeFindManySchema('User').safeParse({ source: 'web' }).success).toBe(true); + expect( + extClient.$zod.makeCreateSchema('User').safeParse({ data: { email: 'u@test.com' }, source: 'web' }) + .success, + ).toBe(true); + }); + }); + + // #endregion + + // #region ZodSchemaFactory standalone constructor + + describe('create factory functions tests', () => { + it('can be constructed directly from client', async () => { + try { + const client = await createTestClient(schema); + const factory = createQuerySchemaFactory(client); + const s = factory.makeFindManySchema('User'); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + } finally { + await client.$disconnect(); + } + }); + + it('can be constructed directly from schema and options and produces equivalent schemas', () => { + const factory = createQuerySchemaFactory(schema); + const s = factory.makeFindManySchema('User'); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); + }); + }); + + // #endregion + + // #region makeProcedureParamSchema + + describe('makeProcedureParamSchema', () => { + it('works with scalar types', () => { + const s = client.$zod.makeProcedureParamSchema({ type: 'String' }); + expect(s.safeParse('hello').success).toBe(true); + expect(s.safeParse(42).success).toBe(false); + }); + + it('works with array types', () => { + const s = client.$zod.makeProcedureParamSchema({ type: 'String', array: true }); + expect(s.safeParse(['a', 'b', 'c']).success).toBe(true); + expect(s.safeParse('a').success).toBe(false); + expect(s.safeParse([1, 2, 3]).success).toBe(false); + }); + + it('works with optional types', () => { + const s = client.$zod.makeProcedureParamSchema({ type: 'String', optional: true }); + expect(s.safeParse('hello').success).toBe(true); + expect(s.safeParse(undefined).success).toBe(true); + expect(s.safeParse(42).success).toBe(false); + }); + + it('works with array and optional types combined', () => { + const s = client.$zod.makeProcedureParamSchema({ type: 'Int', array: true, optional: true }); + expect(s.safeParse([1, 2, 3]).success).toBe(true); + expect(s.safeParse(undefined).success).toBe(true); + expect(s.safeParse(1).success).toBe(false); + }); + + it('throws for unsupported type', () => { + expect(() => client.$zod.makeProcedureParamSchema({ type: 'NotAType' })).toThrow(); + }); + }); + + // #endregion +}); diff --git a/tests/e2e/vitest.config.ts b/tests/e2e/vitest.config.ts index 10606ce6c..f8b260ff4 100644 --- a/tests/e2e/vitest.config.ts +++ b/tests/e2e/vitest.config.ts @@ -6,6 +6,9 @@ export default mergeConfig( defineConfig({ test: { setupFiles: ['@zenstackhq/testtools'], + typecheck: { + enabled: true, + }, }, }), ); From ba735bacd61e69d60da80d2c27aaa224030270f1 Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:39:33 -0800 Subject: [PATCH 06/25] fix(zod): a couple of improvements - Add "create" and "update" variants to model schemas - Simplify dependencies - Translate ZModel meta description into zod meta --- packages/zod/package.json | 5 +- packages/zod/src/error.ts | 2 +- packages/zod/src/factory.ts | 269 ++++++++--------- packages/zod/src/index.ts | 1 + packages/zod/src/types.ts | 127 ++++++++ packages/zod/src/utils.ts | 389 +++++++++++++------------ packages/zod/test/factory.test.ts | 57 ++++ packages/zod/test/schema/schema.ts | 16 +- packages/zod/test/schema/schema.zmodel | 8 +- pnpm-lock.yaml | 9 - 10 files changed, 548 insertions(+), 335 deletions(-) create mode 100644 packages/zod/src/types.ts diff --git a/packages/zod/package.json b/packages/zod/package.json index a7488a784..ad0f66197 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -33,11 +33,8 @@ } }, "dependencies": { - "@zenstackhq/common-helpers": "workspace:*", "@zenstackhq/schema": "workspace:*", - "decimal.js": "catalog:", - "json-stable-stringify": "^1.3.0", - "ts-pattern": "catalog:" + "decimal.js": "catalog:" }, "devDependencies": { "@zenstackhq/eslint-config": "workspace:*", diff --git a/packages/zod/src/error.ts b/packages/zod/src/error.ts index f55dbbba1..cd7bf1e0f 100644 --- a/packages/zod/src/error.ts +++ b/packages/zod/src/error.ts @@ -1,7 +1,7 @@ /** * Error representing failures in Zod schema building. */ -export class ZodSchemaError extends Error { +export class SchemaFactoryError extends Error { constructor(message: string) { super(message); } diff --git a/packages/zod/src/factory.ts b/packages/zod/src/factory.ts index 02e1ab031..97f759690 100644 --- a/packages/zod/src/factory.ts +++ b/packages/zod/src/factory.ts @@ -1,24 +1,23 @@ import { + ExpressionUtils, SchemaAccessor, - type BuiltinType, + type AttributeApplication, type FieldDef, - type FieldIsArray, - type FieldIsRelation, type GetEnum, type GetEnums, - type GetModelFields, - type GetModelFieldType, type GetModels, - type GetTypeDefFields, - type GetTypeDefFieldType, type GetTypeDefs, - type ModelFieldIsOptional, type SchemaDef, - type TypeDefFieldIsOptional, } from '@zenstackhq/schema'; import Decimal from 'decimal.js'; -import { match } from 'ts-pattern'; import z from 'zod'; +import { SchemaFactoryError } from './error'; +import type { + GetModelCreateFieldsShape, + GetModelFieldsShape, + GetModelUpdateFieldsShape, + GetTypeDefFieldsShape, +} from './types'; import { addBigIntValidation, addCustomValidation, @@ -41,10 +40,7 @@ class SchemaFactory { makeModelSchema>( model: Model, ): z.ZodObject, z.core.$strict> { - const modelDef = this.schema.models[model]; - if (!modelDef) { - throw new Error(`Model "${model}" not found in schema`); - } + const modelDef = this.schema.requireModel(model); const fields: Record = {}; for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { @@ -52,17 +48,68 @@ class SchemaFactory { const relatedModelName = fieldDef.type; const lazySchema: z.ZodType = z.lazy(() => this.makeModelSchema(relatedModelName as GetModels)); // relation fields are always optional - fields[fieldName] = this.applyCardinality(lazySchema, fieldDef).optional(); + fields[fieldName] = this.applyDescription( + this.applyCardinality(lazySchema, fieldDef).optional(), + fieldDef.attributes, + ); } else { - fields[fieldName] = this.makeScalarFieldSchema(fieldDef); + fields[fieldName] = this.applyDescription(this.makeScalarFieldSchema(fieldDef), fieldDef.attributes); + } + } + + const shape = z.strictObject(fields); + return this.applyDescription( + addCustomValidation(shape, modelDef.attributes), + modelDef.attributes, + ) as unknown as z.ZodObject, z.core.$strict>; + } + + makeModelCreateSchema>( + model: Model, + ): z.ZodObject, z.core.$strict> { + const modelDef = this.schema.requireModel(model); + const fields: Record = {}; + + for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { + if (fieldDef.relation) { + continue; + } + + let fieldSchema = this.makeScalarFieldSchema(fieldDef); + if (fieldDef.optional || fieldDef.default !== undefined || fieldDef.updatedAt) { + fieldSchema = fieldSchema.optional(); + } + fields[fieldName] = this.applyDescription(fieldSchema, fieldDef.attributes); + } + + const shape = z.strictObject(fields); + return this.applyDescription( + addCustomValidation(shape, modelDef.attributes), + modelDef.attributes, + ) as unknown as z.ZodObject, z.core.$strict>; + } + + makeModelUpdateSchema>( + model: Model, + ): z.ZodObject, z.core.$strict> { + const modelDef = this.schema.requireModel(model); + const fields: Record = {}; + + for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { + if (fieldDef.relation) { + continue; } + + let fieldSchema = this.makeScalarFieldSchema(fieldDef); + fieldSchema = fieldSchema.optional(); + fields[fieldName] = this.applyDescription(fieldSchema, fieldDef.attributes); } const shape = z.strictObject(fields); - return addCustomValidation(shape, modelDef.attributes) as unknown as z.ZodObject< - GetModelFieldsShape, - z.core.$strict - >; + return this.applyDescription( + addCustomValidation(shape, modelDef.attributes), + modelDef.attributes, + ) as unknown as z.ZodObject, z.core.$strict>; } private makeScalarFieldSchema(fieldDef: FieldDef): z.ZodType { @@ -80,24 +127,47 @@ class SchemaFactory { return this.applyCardinality(this.makeTypeSchema(type as GetTypeDefs), fieldDef); } - const base = match(type as BuiltinType) - .with('String', () => addStringValidation(z.string(), attributes)) - .with('Int', () => addNumberValidation(z.number().int(), attributes)) - .with('Float', () => addNumberValidation(z.number(), attributes)) - .with('Boolean', () => z.boolean()) - .with('BigInt', () => addBigIntValidation(z.bigint(), attributes)) - .with('Decimal', () => - z.union([ + let base: z.ZodType; + switch (type) { + case 'String': + base = addStringValidation(z.string(), attributes); + break; + case 'Int': + base = addNumberValidation(z.number().int(), attributes); + break; + case 'Float': + base = addNumberValidation(z.number(), attributes); + break; + case 'Boolean': + base = z.boolean(); + break; + case 'BigInt': + base = addBigIntValidation(z.bigint(), attributes); + break; + case 'Decimal': + base = z.union([ addNumberValidation(z.number(), attributes) as z.ZodNumber, addDecimalValidation(z.string(), attributes, true) as z.ZodString, addDecimalValidation(z.instanceof(Decimal), attributes, true), - ]), - ) - .with('DateTime', () => z.union([z.date(), z.iso.datetime()])) - .with('Bytes', () => z.instanceof(Uint8Array)) - .with('Json', () => this.makeJsonSchema()) - .with('Unsupported', () => z.unknown()) - .exhaustive(); + ]); + break; + case 'DateTime': + base = z.union([z.date(), z.iso.datetime()]); + break; + case 'Bytes': + base = z.instanceof(Uint8Array); + break; + case 'Json': + base = this.makeJsonSchema(); + break; + case 'Unsupported': + base = z.unknown(); + break; + default: { + const _exhaustive: never = type as never; + throw new SchemaFactoryError(`Unsupported field type: ${_exhaustive}`); + } + } return this.applyCardinality(base, fieldDef); } @@ -131,113 +201,48 @@ class SchemaFactory { const fields: Record = {}; for (const [fieldName, fieldDef] of Object.entries(typeDef.fields)) { - fields[fieldName] = this.makeScalarFieldSchema(fieldDef); + fields[fieldName] = this.applyDescription(this.makeScalarFieldSchema(fieldDef), fieldDef.attributes); } const shape = z.strictObject(fields); - return addCustomValidation(shape, typeDef.attributes) as unknown as z.ZodObject< - GetTypeDefFieldsShape, - z.core.$strict - >; + return this.applyDescription( + addCustomValidation(shape, typeDef.attributes), + typeDef.attributes, + ) as unknown as z.ZodObject, z.core.$strict>; } makeEnumSchema>( _enum: Enum, ): z.ZodEnum<{ [Key in keyof GetEnum]: GetEnum[Key] }> { const enumDef = this.schema.requireEnum(_enum); - return z.enum(Object.keys(enumDef.values) as [string, ...string[]]) as unknown as z.ZodEnum<{ + const schema = z.enum(Object.keys(enumDef.values) as [string, ...string[]]); + return this.applyDescription(schema, enumDef.attributes) as unknown as z.ZodEnum<{ [Key in keyof GetEnum]: GetEnum[Key]; }>; } -} -type GetModelFieldsShape> = { - // scalar fields - [Field in GetModelFields as FieldIsRelation extends true - ? never - : Field]: ZodOptionalAndNullableIf< - MapModelFieldToZod, - ModelFieldIsOptional - >; -} & { - // relation fields, always optional - [Field in GetModelFields as FieldIsRelation extends true - ? Field - : never]: ZodNullableIf< - z.ZodOptional< - ZodArrayIf< - z.ZodObject< - GetModelFieldsShape< - Schema, - GetModelFieldType extends GetModels - ? GetModelFieldType - : never - >, - z.core.$strict - >, - FieldIsArray - > - >, - ModelFieldIsOptional - >; -}; - -type GetTypeDefFieldsShape> = { - [Field in GetTypeDefFields]: ZodOptionalAndNullableIf< - MapTypeDefFieldToZod, - TypeDefFieldIsOptional - >; -}; - -type FieldTypeZodMap = { - String: z.ZodString; - Int: z.ZodNumber; - BigInt: z.ZodBigInt; - Float: z.ZodNumber; - Decimal: z.ZodType; - Boolean: z.ZodBoolean; - DateTime: z.ZodType; - Bytes: z.ZodType; - Json: JsonZodType; -}; - -type MapModelFieldToZod< - Schema extends SchemaDef, - Model extends GetModels, - Field extends GetModelFields, - FieldType = GetModelFieldType, -> = MapFieldTypeToZod; - -type MapTypeDefFieldToZod< - Schema extends SchemaDef, - Type extends GetTypeDefs, - Field extends GetTypeDefFields, - FieldType = GetTypeDefFieldType, -> = MapFieldTypeToZod; - -type MapFieldTypeToZod = FieldType extends keyof FieldTypeZodMap - ? FieldTypeZodMap[FieldType] - : FieldType extends GetEnums - ? EnumZodType - : FieldType extends GetTypeDefs - ? z.ZodObject, z.core.$strict> - : z.ZodUnknown; - -type JsonZodType = - | z.ZodObject, z.core.$loose> - | z.ZodArray - | z.ZodString - | z.ZodNumber - | z.ZodBoolean - | z.ZodNull; - -type EnumZodType> = z.ZodEnum<{ - [Key in keyof GetEnum]: GetEnum[Key]; -}>; - -type ZodOptionalAndNullableIf = Condition extends true - ? z.ZodOptional> - : T; - -type ZodNullableIf = Condition extends true ? z.ZodNullable : T; -type ZodArrayIf = Condition extends true ? z.ZodArray : T; + private getMetaDescription(attributes: readonly AttributeApplication[] | undefined): string | undefined { + if (!attributes) return undefined; + for (const attr of attributes) { + if (attr.name !== '@meta' && attr.name !== '@@meta') continue; + const nameExpr = attr.args?.[0]?.value; + if (!nameExpr || ExpressionUtils.getLiteralValue(nameExpr) !== 'description') continue; + const valueExpr = attr.args?.[1]?.value; + if (valueExpr) { + return ExpressionUtils.getLiteralValue(valueExpr) as string | undefined; + } + } + return undefined; + } + + private applyDescription( + schema: T, + attributes: readonly AttributeApplication[] | undefined, + ): T { + const description = this.getMetaDescription(attributes); + if (description) { + return schema.meta({ description }) as T; + } + return schema; + } +} diff --git a/packages/zod/src/index.ts b/packages/zod/src/index.ts index 905551618..667815bbe 100644 --- a/packages/zod/src/index.ts +++ b/packages/zod/src/index.ts @@ -1,2 +1,3 @@ export { createSchemaFactory as createModelSchemaFactory } from './factory'; +export type * from './types'; export * as ZodUtils from './utils'; diff --git a/packages/zod/src/types.ts b/packages/zod/src/types.ts new file mode 100644 index 000000000..6bc5a7c80 --- /dev/null +++ b/packages/zod/src/types.ts @@ -0,0 +1,127 @@ +import type { + FieldHasDefault, + FieldIsArray, + FieldIsRelation, + GetEnum, + GetEnums, + GetModelFields, + GetModelFieldType, + GetModels, + GetTypeDefFields, + GetTypeDefFieldType, + GetTypeDefs, + ModelFieldIsOptional, + SchemaDef, + TypeDefFieldIsOptional, +} from '@zenstackhq/schema'; +import type Decimal from 'decimal.js'; +import type z from 'zod'; + +export type GetModelFieldsShape> = { + // scalar fields + [Field in GetModelFields as FieldIsRelation extends true + ? never + : Field]: ZodOptionalAndNullableIf< + MapModelFieldToZod, + ModelFieldIsOptional + >; +} & { + // relation fields, always optional + [Field in GetModelFields as FieldIsRelation extends true + ? Field + : never]: ZodNullableIf< + z.ZodOptional< + ZodArrayIf< + z.ZodObject< + GetModelFieldsShape< + Schema, + GetModelFieldType extends GetModels + ? GetModelFieldType + : never + >, + z.core.$strict + >, + FieldIsArray + > + >, + ModelFieldIsOptional + >; +}; + +export type GetModelCreateFieldsShape> = { + [Field in GetModelFields as FieldIsRelation extends true + ? never + : Field]: ZodOptionalIf< + ZodOptionalAndNullableIf, ModelFieldIsOptional>, + FieldHasDefault + >; +}; + +export type GetModelUpdateFieldsShape> = { + [Field in GetModelFields as FieldIsRelation extends true + ? never + : Field]: z.ZodOptional< + ZodOptionalAndNullableIf, ModelFieldIsOptional> + >; +}; + +export type GetTypeDefFieldsShape> = { + [Field in GetTypeDefFields]: ZodOptionalAndNullableIf< + MapTypeDefFieldToZod, + TypeDefFieldIsOptional + >; +}; + +type FieldTypeZodMap = { + String: z.ZodString; + Int: z.ZodNumber; + BigInt: z.ZodBigInt; + Float: z.ZodNumber; + Decimal: z.ZodType; + Boolean: z.ZodBoolean; + DateTime: z.ZodType; + Bytes: z.ZodType; + Json: JsonZodType; +}; + +type MapModelFieldToZod< + Schema extends SchemaDef, + Model extends GetModels, + Field extends GetModelFields, + FieldType = GetModelFieldType, +> = MapFieldTypeToZod; + +type MapTypeDefFieldToZod< + Schema extends SchemaDef, + Type extends GetTypeDefs, + Field extends GetTypeDefFields, + FieldType = GetTypeDefFieldType, +> = MapFieldTypeToZod; + +type MapFieldTypeToZod = FieldType extends keyof FieldTypeZodMap + ? FieldTypeZodMap[FieldType] + : FieldType extends GetEnums + ? EnumZodType + : FieldType extends GetTypeDefs + ? z.ZodObject, z.core.$strict> + : z.ZodUnknown; + +type JsonZodType = + | z.ZodObject, z.core.$loose> + | z.ZodArray + | z.ZodString + | z.ZodNumber + | z.ZodBoolean + | z.ZodNull; + +type EnumZodType> = z.ZodEnum<{ + [Key in keyof GetEnum]: GetEnum[Key]; +}>; + +type ZodOptionalAndNullableIf = Condition extends true + ? z.ZodOptional> + : T; + +type ZodOptionalIf = Condition extends true ? z.ZodOptional : T; +type ZodNullableIf = Condition extends true ? z.ZodNullable : T; +type ZodArrayIf = Condition extends true ? z.ZodArray : T; diff --git a/packages/zod/src/utils.ts b/packages/zod/src/utils.ts index b670c7316..989f58761 100644 --- a/packages/zod/src/utils.ts +++ b/packages/zod/src/utils.ts @@ -1,4 +1,3 @@ -import { invariant } from '@zenstackhq/common-helpers'; import { ExpressionUtils, type AttributeApplication, @@ -10,9 +9,8 @@ import { type UnaryExpression, } from '@zenstackhq/schema'; import Decimal from 'decimal.js'; -import { match, P } from 'ts-pattern'; import { z } from 'zod'; -import { ZodSchemaError } from './error'; +import { SchemaFactoryError } from './error'; function getArgValue(expr: Expression | undefined): T | undefined { if (!expr || !ExpressionUtils.isLiteral(expr)) { @@ -31,8 +29,8 @@ export function addStringValidation( let result = schema; for (const attr of attributes) { - match(attr.name) - .with('@length', () => { + switch (attr.name) { + case '@length': { const min = getArgValue(attr.args?.[0]?.value); if (min !== undefined) { result = result.min(min); @@ -41,49 +39,55 @@ export function addStringValidation( if (max !== undefined) { result = result.max(max); } - }) - .with('@startsWith', () => { + break; + } + case '@startsWith': { const value = getArgValue(attr.args?.[0]?.value); if (value !== undefined) { result = result.startsWith(value); } - }) - .with('@endsWith', () => { + break; + } + case '@endsWith': { const value = getArgValue(attr.args?.[0]?.value); if (value !== undefined) { result = result.endsWith(value); } - }) - .with('@contains', () => { + break; + } + case '@contains': { const value = getArgValue(attr.args?.[0]?.value); if (value !== undefined) { result = result.includes(value); } - }) - .with('@regex', () => { + break; + } + case '@regex': { const pattern = getArgValue(attr.args?.[0]?.value); if (pattern !== undefined) { result = result.regex(new RegExp(pattern)); } - }) - .with('@email', () => { + break; + } + case '@email': result = result.email(); - }) - .with('@datetime', () => { + break; + case '@datetime': result = result.datetime(); - }) - .with('@url', () => { + break; + case '@url': result = result.url(); - }) - .with('@trim', () => { + break; + case '@trim': result = result.trim(); - }) - .with('@lower', () => { + break; + case '@lower': result = result.toLowerCase(); - }) - .with('@upper', () => { + break; + case '@upper': result = result.toUpperCase(); - }); + break; + } } return result; } @@ -102,19 +106,20 @@ export function addNumberValidation( if (val === undefined) { continue; } - match(attr.name) - .with('@gt', () => { + switch (attr.name) { + case '@gt': result = result.gt(val); - }) - .with('@gte', () => { + break; + case '@gte': result = result.gte(val); - }) - .with('@lt', () => { + break; + case '@lt': result = result.lt(val); - }) - .with('@lte', () => { + break; + case '@lte': result = result.lte(val); - }); + break; + } } return result; } @@ -134,19 +139,20 @@ export function addBigIntValidation( continue; } - match(attr.name) - .with('@gt', () => { + switch (attr.name) { + case '@gt': result = result.gt(BigInt(val)); - }) - .with('@gte', () => { + break; + case '@gte': result = result.gte(BigInt(val)); - }) - .with('@lt', () => { + break; + case '@lt': result = result.lt(BigInt(val)); - }) - .with('@lte', () => { + break; + case '@lte': result = result.lte(BigInt(val)); - }); + break; + } } return result; } @@ -211,19 +217,20 @@ export function addDecimalValidation( continue; } - match(attr.name) - .with('@gt', () => { + switch (attr.name) { + case '@gt': result = refine(result, 'gt', val); - }) - .with('@gte', () => { + break; + case '@gte': result = refine(result, 'gte', val); - }) - .with('@lt', () => { + break; + case '@lt': result = refine(result, 'lt', val); - }) - .with('@lte', () => { + break; + case '@lte': result = refine(result, 'lte', val); - }); + break; + } } } @@ -240,18 +247,16 @@ export function addListValidation( let result = schema; for (const attr of attributes) { - match(attr.name) - .with('@length', () => { - const min = getArgValue(attr.args?.[0]?.value); - if (min !== undefined) { - result = result.min(min); - } - const max = getArgValue(attr.args?.[1]?.value); - if (max !== undefined) { - result = result.max(max); - } - }) - .otherwise(() => {}); + if (attr.name === '@length') { + const min = getArgValue(attr.args?.[0]?.value); + if (min !== undefined) { + result = result.min(min); + } + const max = getArgValue(attr.args?.[1]?.value); + if (max !== undefined) { + result = result.max(max); + } + } } return result; } @@ -299,20 +304,28 @@ function applyValidation( } function evalExpression(data: any, expr: Expression): unknown { - return match(expr) - .with({ kind: 'literal' }, (e) => e.value) - .with({ kind: 'array' }, (e) => e.items.map((item) => evalExpression(data, item))) - .with({ kind: 'field' }, (e) => evalField(data, e)) - .with({ kind: 'member' }, (e) => evalMember(data, e)) - .with({ kind: 'unary' }, (e) => evalUnary(data, e)) - .with({ kind: 'binary' }, (e) => evalBinary(data, e)) - .with({ kind: 'call' }, (e) => evalCall(data, e)) - .with({ kind: 'this' }, () => data ?? null) - .with({ kind: 'null' }, () => null) - .with({ kind: 'binding' }, () => { - throw new Error('Binding expression is not supported in validation expressions'); - }) - .exhaustive(); + switch (expr.kind) { + case 'literal': + return expr.value; + case 'array': + return expr.items.map((item) => evalExpression(data, item)); + case 'field': + return evalField(data, expr); + case 'member': + return evalMember(data, expr); + case 'unary': + return evalUnary(data, expr); + case 'binary': + return evalBinary(data, expr); + case 'call': + return evalCall(data, expr); + case 'this': + return data ?? null; + case 'null': + return null; + case 'binding': + throw new SchemaFactoryError('Binding expression is not supported in validation expressions'); + } } function evalField(data: any, e: FieldExpression) { @@ -325,47 +338,51 @@ function evalUnary(data: any, expr: UnaryExpression) { case '!': return !operand; default: - throw new Error(`Unsupported unary operator: ${expr.op}`); + throw new SchemaFactoryError(`Unsupported unary operator: ${expr.op}`); } } function evalBinary(data: any, expr: BinaryExpression) { const left = evalExpression(data, expr.left); const right = evalExpression(data, expr.right); - return match(expr.op) - .with('&&', () => Boolean(left) && Boolean(right)) - .with('||', () => Boolean(left) || Boolean(right)) - .with('==', () => left == right) - .with('!=', () => left != right) - .with('<', () => (left as any) < (right as any)) - .with('<=', () => (left as any) <= (right as any)) - .with('>', () => (left as any) > (right as any)) - .with('>=', () => (left as any) >= (right as any)) - .with('?', () => { + switch (expr.op) { + case '&&': + return Boolean(left) && Boolean(right); + case '||': + return Boolean(left) || Boolean(right); + case '==': + return left == right; + case '!=': + return left != right; + case '<': + return (left as any) < (right as any); + case '<=': + return (left as any) <= (right as any); + case '>': + return (left as any) > (right as any); + case '>=': + return (left as any) >= (right as any); + case '?': if (!Array.isArray(left)) { return false; } return left.some((item) => item === right); - }) - .with('!', () => { + case '!': if (!Array.isArray(left)) { return false; } return left.every((item) => item === right); - }) - .with('^', () => { + case '^': if (!Array.isArray(left)) { return false; } return !left.some((item) => item === right); - }) - .with('in', () => { + case 'in': if (!Array.isArray(right)) { return false; } return right.includes(left); - }) - .exhaustive(); + } } function evalMember(data: any, expr: MemberExpression) { @@ -380,93 +397,101 @@ function evalMember(data: any, expr: MemberExpression) { } function evalCall(data: any, expr: CallExpression) { + const f = expr.function; const fieldArg = expr.args?.[0] ? evalExpression(data, expr.args[0]) : undefined; - return ( - match(expr.function) - // string functions - .with('length', (f) => { - if (fieldArg === undefined || fieldArg === null) { - return false; - } - invariant( - typeof fieldArg === 'string' || Array.isArray(fieldArg), - `"${f}" first argument must be a string or a list`, - ); - return fieldArg.length; - }) - .with(P.union('startsWith', 'endsWith', 'contains'), (f) => { - if (fieldArg === undefined || fieldArg === null) { - return false; - } - invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); - invariant(expr.args?.[1], `"${f}" requires a search argument`); - const search = getArgValue(expr.args?.[1])!; - const caseInsensitive = getArgValue(expr.args?.[2]) ?? false; + switch (f) { + // string functions + case 'length': { + if (fieldArg === undefined || fieldArg === null) { + return false; + } + invariant( + typeof fieldArg === 'string' || Array.isArray(fieldArg), + `"${f}" first argument must be a string or a list`, + ); + return fieldArg.length; + } + case 'startsWith': + case 'endsWith': + case 'contains': { + if (fieldArg === undefined || fieldArg === null) { + return false; + } + invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); + invariant(expr.args?.[1], `"${f}" requires a search argument`); - const matcher = (x: string, y: string) => - match(f) - .with('startsWith', () => x.startsWith(y)) - .with('endsWith', () => x.endsWith(y)) - .with('contains', () => x.includes(y)) - .exhaustive(); - return caseInsensitive - ? matcher(fieldArg.toLowerCase(), search.toLowerCase()) - : matcher(fieldArg, search); - }) - .with('regex', (f) => { - if (fieldArg === undefined || fieldArg === null) { - return false; - } - invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); - const pattern = getArgValue(expr.args?.[1])!; - invariant(pattern !== undefined, `"${f}" requires a pattern argument`); - return new RegExp(pattern).test(fieldArg); - }) - .with(P.union('isEmail', 'isUrl', 'isDateTime'), (f) => { - if (fieldArg === undefined || fieldArg === null) { - return false; - } - invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); - const fn = match(f) - .with('isEmail', () => 'email' as const) - .with('isUrl', () => 'url' as const) - .with('isDateTime', () => 'datetime' as const) - .exhaustive(); - return z.string()[fn]().safeParse(fieldArg).success; - }) - // list functions - .with(P.union('has', 'hasEvery', 'hasSome'), (f) => { - invariant(expr.args?.[1], `${f} requires a search argument`); - if (fieldArg === undefined || fieldArg === null) { - return false; - } - invariant(Array.isArray(fieldArg), `"${f}" first argument must be an array field`); + const search = getArgValue(expr.args?.[1])!; + const caseInsensitive = getArgValue(expr.args?.[2]) ?? false; - const search = evalExpression(data, expr.args?.[1])!; - const matcher = (x: any[], y: any) => - match(f) - .with('has', () => x.some((item) => item === y)) - .with('hasEvery', () => { - invariant(Array.isArray(y), 'hasEvery second argument must be an array'); - return y.every((v) => x.some((item) => item === v)); - }) - .with('hasSome', () => { - invariant(Array.isArray(y), 'hasSome second argument must be an array'); - return y.some((v) => x.some((item) => item === v)); - }) - .exhaustive(); - return matcher(fieldArg, search); - }) - .with('isEmpty', (f) => { - if (fieldArg === undefined || fieldArg === null) { - return false; + const applyStringOp = (x: string, y: string) => { + switch (f) { + case 'startsWith': + return x.startsWith(y); + case 'endsWith': + return x.endsWith(y); + case 'contains': + return x.includes(y); } - invariant(Array.isArray(fieldArg), `"${f}" first argument must be an array field`); - return fieldArg.length === 0; - }) - .otherwise(() => { - throw new ZodSchemaError(`Unsupported function "${expr.function}"`); - }) - ); + }; + return caseInsensitive + ? applyStringOp(fieldArg.toLowerCase(), search.toLowerCase()) + : applyStringOp(fieldArg, search); + } + case 'regex': { + if (fieldArg === undefined || fieldArg === null) { + return false; + } + invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); + const pattern = getArgValue(expr.args?.[1])!; + invariant(pattern !== undefined, `"${f}" requires a pattern argument`); + return new RegExp(pattern).test(fieldArg); + } + case 'isEmail': + case 'isUrl': + case 'isDateTime': { + if (fieldArg === undefined || fieldArg === null) { + return false; + } + invariant(typeof fieldArg === 'string', `"${f}" first argument must be a string`); + const fn = f === 'isEmail' ? ('email' as const) : f === 'isUrl' ? ('url' as const) : ('datetime' as const); + return z.string()[fn]().safeParse(fieldArg).success; + } + // list functions + case 'has': + case 'hasEvery': + case 'hasSome': { + invariant(expr.args?.[1], `${f} requires a search argument`); + if (fieldArg === undefined || fieldArg === null) { + return false; + } + invariant(Array.isArray(fieldArg), `"${f}" first argument must be an array field`); + + const search = evalExpression(data, expr.args?.[1])!; + if (f === 'has') { + return fieldArg.some((item) => item === search); + } else if (f === 'hasEvery') { + invariant(Array.isArray(search), 'hasEvery second argument must be an array'); + return search.every((v) => fieldArg.some((item) => item === v)); + } else { + invariant(Array.isArray(search), 'hasSome second argument must be an array'); + return search.some((v) => fieldArg.some((item) => item === v)); + } + } + case 'isEmpty': { + if (fieldArg === undefined || fieldArg === null) { + return false; + } + invariant(Array.isArray(fieldArg), `"${f}" first argument must be an array field`); + return fieldArg.length === 0; + } + default: + throw new SchemaFactoryError(`Unsupported function "${f}"`); + } +} + +function invariant(condition: unknown, message?: string): asserts condition { + if (!condition) { + throw new SchemaFactoryError(message ?? 'Invariant failed'); + } } diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 7283e1d91..8ebf2aafc 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -552,6 +552,63 @@ describe('SchemaFactory - makeTypeSchema', () => { }); }); +describe('SchemaFactory - @meta description', () => { + it('applies @@meta description to model schema', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.meta()?.description).toBe('A user of the system'); + }); + + it('applies @meta description to model field schema', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.shape.email.meta()?.description).toBe("The user's email address"); + }); + + it('does not set description when @meta("description") is absent', () => { + const userSchema = factory.makeModelSchema('User'); + expect(userSchema.shape.active.meta()?.description).toBeUndefined(); + }); + + it('applies @@meta description to model create schema', () => { + const createSchema = factory.makeModelCreateSchema('User'); + expect(createSchema.meta()?.description).toBe('A user of the system'); + }); + + it('applies @meta description to model create field schema', () => { + const createSchema = factory.makeModelCreateSchema('User'); + expect(createSchema.shape.email.meta()?.description).toBe("The user's email address"); + }); + + it('applies @@meta description to model update schema', () => { + const updateSchema = factory.makeModelUpdateSchema('User'); + expect(updateSchema.meta()?.description).toBe('A user of the system'); + }); + + it('applies @meta description to model update field schema', () => { + const updateSchema = factory.makeModelUpdateSchema('User'); + expect(updateSchema.shape.email.meta()?.description).toBe("The user's email address"); + }); + + it('applies @@meta description to typedef schema', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.meta()?.description).toBe('A mailing address'); + }); + + it('applies @meta description to typedef field schema', () => { + const addressSchema = factory.makeTypeSchema('Address'); + expect(addressSchema.shape.street.meta()?.description).toBe('Street address line'); + }); + + it('applies @@meta description to enum schema', () => { + const statusSchema = factory.makeEnumSchema('Status'); + expect(statusSchema.meta()?.description).toBe('User account status'); + }); + + it('does not set description for model without @@meta("description")', () => { + const postSchema = factory.makeModelSchema('Post'); + expect(postSchema.meta()?.description).toBeUndefined(); + }); +}); + describe('SchemaFactory - makeEnumSchema', () => { it('accepts all valid enum values', () => { const statusSchema = factory.makeEnumSchema('Status'); diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index ae8c3be32..1336683ef 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -24,7 +24,7 @@ export class SchemaType implements SchemaDef { email: { name: "email", type: "String", - attributes: [{ name: "@email" }] + attributes: [{ name: "@email" }, { name: "@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("The user's email address") }] }] }, username: { name: "username", @@ -99,7 +99,8 @@ export class SchemaType implements SchemaDef { } }, attributes: [ - { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("age"), ">=", ExpressionUtils.literal(18)) }, { name: "message", value: ExpressionUtils.literal("Must be adult") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("age")]) }] } + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.field("age"), ">=", ExpressionUtils.literal(18)) }, { name: "message", value: ExpressionUtils.literal("Must be adult") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("age")]) }] }, + { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A user of the system") }] } ], idFields: ["id"], uniqueFields: { @@ -152,7 +153,8 @@ export class SchemaType implements SchemaDef { fields: { street: { name: "street", - type: "String" + type: "String", + attributes: [{ name: "@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("Street address line") }] }] }, city: { name: "city", @@ -166,7 +168,8 @@ export class SchemaType implements SchemaDef { } }, attributes: [ - { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] } + { name: "@@validate", args: [{ name: "value", value: ExpressionUtils.binary(ExpressionUtils.binary(ExpressionUtils.field("zip"), "==", ExpressionUtils._null()), "||", ExpressionUtils.binary(ExpressionUtils.call("length", [ExpressionUtils.field("zip")]), "==", ExpressionUtils.literal(5))) }, { name: "message", value: ExpressionUtils.literal("Zip code must be exactly 5 characters") }, { name: "path", value: ExpressionUtils.array("String", [ExpressionUtils.literal("zip")]) }] }, + { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("A mailing address") }] } ] } } as const; @@ -177,7 +180,10 @@ export class SchemaType implements SchemaDef { ACTIVE: "ACTIVE", INACTIVE: "INACTIVE", PENDING: "PENDING" - } + }, + attributes: [ + { name: "@@meta", args: [{ name: "name", value: ExpressionUtils.literal("description") }, { name: "value", value: ExpressionUtils.literal("User account status") }] } + ] } } as const; authType = "User" as const; diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index fd651c927..348206ac2 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -6,19 +6,22 @@ enum Status { ACTIVE INACTIVE PENDING + + @@meta("description", "User account status") } type Address { - street String + street String @meta("description", "Street address line") city String @length(2) zip String? @@validate(zip == null || length(zip) == 5, "Zip code must be exactly 5 characters", ["zip"]) + @@meta("description", "A mailing address") } model User { id String @id @default(cuid()) - email String @email + email String @email @meta("description", "The user's email address") username String @length(3, 50) website String? @url code String @startsWith("USR") @@ -35,6 +38,7 @@ model User { posts Post[] @@validate(age >= 18, "Must be adult", ["age"]) + @@meta("description", "A user of the system") } model Post { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b484975aa..f5a1e0575 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -834,21 +834,12 @@ importers: packages/zod: dependencies: - '@zenstackhq/common-helpers': - specifier: workspace:* - version: link:../common-helpers '@zenstackhq/schema': specifier: workspace:* version: link:../schema decimal.js: specifier: 'catalog:' version: 10.6.0 - json-stable-stringify: - specifier: ^1.3.0 - version: 1.3.0 - ts-pattern: - specifier: 'catalog:' - version: 5.7.1 devDependencies: '@zenstackhq/eslint-config': specifier: workspace:* From 960b861f6f165e09bcf21535b342fc86194f436e Mon Sep 17 00:00:00 2001 From: ymc9 <104139426+ymc9@users.noreply.github.com> Date: Fri, 20 Feb 2026 21:53:04 -0800 Subject: [PATCH 07/25] address PR comments --- packages/zod/src/utils.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/zod/src/utils.ts b/packages/zod/src/utils.ts index 989f58761..27f3c8579 100644 --- a/packages/zod/src/utils.ts +++ b/packages/zod/src/utils.ts @@ -382,6 +382,8 @@ function evalBinary(data: any, expr: BinaryExpression) { return false; } return right.includes(left); + default: + throw new SchemaFactoryError(`Unsupported binary operator: ${expr.op}`); } } From 1e70052deddd936aa86f66cea4f06f229264987b Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Sat, 21 Feb 2026 12:08:31 -0800 Subject: [PATCH 08/25] feat(zod): introduce an option to control depth of the built zod schemas (#2392) --- packages/orm/src/client/zod/factory.ts | 352 +++++++++++++++++-------- packages/zod/src/index.ts | 3 +- packages/zod/test/factory.test.ts | 4 +- pnpm-lock.yaml | 333 ++++++++++++++++++++++- samples/nuxt/package.json | 7 +- tests/e2e/orm/client-api/zod.test.ts | 315 ++++++++++++++++++++++ 6 files changed, 884 insertions(+), 130 deletions(-) diff --git a/packages/orm/src/client/zod/factory.ts b/packages/orm/src/client/zod/factory.ts index 8ad67de6b..8ef8446ab 100644 --- a/packages/orm/src/client/zod/factory.ts +++ b/packages/orm/src/client/zod/factory.ts @@ -87,6 +87,16 @@ export function createQuerySchemaFactory(clientOrSchema: any, options?: any) { return new ZodSchemaFactory(clientOrSchema, options); } +/** + * Options for creating Zod schemas. + */ +export type CreateSchemaOptions = { + /** + * Controls the depth of relation nesting in the generated schema. Default is unlimited. + */ + relationDepth?: number; +}; + /** * Factory class responsible for creating and caching Zod schemas for ORM input validation. */ @@ -120,6 +130,16 @@ export class ZodSchemaFactory< return this.options.validateInput !== false; } + private shouldIncludeRelations(options?: CreateSchemaOptions): boolean { + return options?.relationDepth === undefined || options.relationDepth > 0; + } + + private nextOptions(options?: CreateSchemaOptions): CreateSchemaOptions | undefined { + if (!options) return undefined; + if (options.relationDepth === undefined) return options; + return { ...options, relationDepth: options.relationDepth - 1 }; + } + // #region Cache Management // @ts-ignore @@ -148,42 +168,45 @@ export class ZodSchemaFactory< makeFindUniqueSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { - return this.makeFindSchema(model, 'findUnique') as ZodType< + return this.makeFindSchema(model, 'findUnique', options) as ZodType< FindUniqueArgs >; } makeFindFirstSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType | undefined> { - return this.makeFindSchema(model, 'findFirst') as ZodType< + return this.makeFindSchema(model, 'findFirst', options) as ZodType< FindFirstArgs | undefined >; } makeFindManySchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType | undefined> { - return this.makeFindSchema(model, 'findMany') as ZodType< + return this.makeFindSchema(model, 'findMany', options) as ZodType< FindManyArgs | undefined >; } @cache() - private makeFindSchema(model: string, operation: CoreCrudOperations) { + private makeFindSchema(model: string, operation: CoreCrudOperations, options?: CreateSchemaOptions) { const fields: Record = {}; const unique = operation === 'findUnique'; const findOne = operation === 'findUnique' || operation === 'findFirst'; - const where = this.makeWhereSchema(model, unique); + const where = this.makeWhereSchema(model, unique, false, false, options); if (unique) { fields['where'] = where; } else { fields['where'] = where.optional(); } - fields['select'] = this.makeSelectSchema(model).optional().nullable(); - fields['include'] = this.makeIncludeSchema(model).optional().nullable(); + fields['select'] = this.makeSelectSchema(model, options).optional().nullable(); + fields['include'] = this.makeIncludeSchema(model, options).optional().nullable(); fields['omit'] = this.makeOmitSchema(model).optional().nullable(); if (!unique) { @@ -193,8 +216,11 @@ export class ZodSchemaFactory< } else { fields['take'] = this.makeTakeSchema().optional(); } - fields['orderBy'] = this.orArray(this.makeOrderBySchema(model, true, false), true).optional(); - fields['cursor'] = this.makeCursorSchema(model).optional(); + fields['orderBy'] = this.orArray( + this.makeOrderBySchema(model, true, false, options), + true, + ).optional(); + fields['cursor'] = this.makeCursorSchema(model, options).optional(); fields['distinct'] = this.makeDistinctSchema(model).optional(); } @@ -212,9 +238,10 @@ export class ZodSchemaFactory< @cache() makeExistsSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType | undefined> { const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), + where: this.makeWhereSchema(model, false, false, false, options).optional(), }); return this.mergePluginArgsSchema(baseSchema, 'exists').optional() as ZodType< ExistsArgs | undefined @@ -306,7 +333,13 @@ export class ZodSchemaFactory< } @cache() - makeWhereSchema(model: string, unique: boolean, withoutRelationFields = false, withAggregations = false): ZodType { + makeWhereSchema( + model: string, + unique: boolean, + withoutRelationFields = false, + withAggregations = false, + options?: CreateSchemaOptions, + ): ZodType { const modelDef = requireModel(this.schema, model); // unique field used in unique filters bypass filter slicing @@ -320,13 +353,15 @@ export class ZodSchemaFactory< .map((uf) => uf.name) : undefined; + const nextOpts = this.nextOptions(options); + const fields: Record = {}; for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); let fieldSchema: ZodType | undefined; if (fieldDef.relation) { - if (withoutRelationFields) { + if (withoutRelationFields || !this.shouldIncludeRelations(options)) { continue; } @@ -336,7 +371,9 @@ export class ZodSchemaFactory< // Relation filters are not allowed for this field - use z.never() fieldSchema = z.never(); } else { - fieldSchema = z.lazy(() => this.makeWhereSchema(fieldDef.type, false).optional()); + fieldSchema = z.lazy(() => + this.makeWhereSchema(fieldDef.type, false, false, false, nextOpts).optional(), + ); // optional to-one relation allows null fieldSchema = this.nullableIf(fieldSchema, !fieldDef.array && !!fieldDef.optional); @@ -424,15 +461,15 @@ export class ZodSchemaFactory< // logical operators fields['AND'] = this.orArray( - z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)), + z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields, false, options)), true, ).optional(); fields['OR'] = z - .lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)) + .lazy(() => this.makeWhereSchema(model, false, withoutRelationFields, false, options)) .array() .optional(); fields['NOT'] = this.orArray( - z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields)), + z.lazy(() => this.makeWhereSchema(model, false, withoutRelationFields, false, options)), true, ).optional(); @@ -842,34 +879,40 @@ export class ZodSchemaFactory< } @cache() - private makeSelectSchema(model: string) { + private makeSelectSchema(model: string, options?: CreateSchemaOptions) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { + if (!this.shouldIncludeRelations(options)) { + continue; + } // Check if the target model is allowed by slicing configuration if (this.isModelAllowed(fieldDef.type)) { - fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + fields[field] = this.makeRelationSelectIncludeSchema(model, field, options).optional(); } } else { fields[field] = z.boolean().optional(); } } - const _countSchema = this.makeCountSelectionSchema(model); - if (!(_countSchema instanceof z.ZodNever)) { - fields['_count'] = _countSchema; + if (this.shouldIncludeRelations(options)) { + const _countSchema = this.makeCountSelectionSchema(model, options); + if (!(_countSchema instanceof z.ZodNever)) { + fields['_count'] = _countSchema; + } } return z.strictObject(fields); } @cache() - private makeCountSelectionSchema(model: string) { + private makeCountSelectionSchema(model: string, options?: CreateSchemaOptions) { const modelDef = requireModel(this.schema, model); const toManyRelations = Object.values(modelDef.fields).filter((def) => def.relation && def.array); if (toManyRelations.length > 0) { + const nextOpts = this.nextOptions(options); return z .union([ z.literal(true), @@ -882,7 +925,13 @@ export class ZodSchemaFactory< .union([ z.boolean(), z.strictObject({ - where: this.makeWhereSchema(fieldDef.type, false, false), + where: this.makeWhereSchema( + fieldDef.type, + false, + false, + false, + nextOpts, + ), }), ]) .optional(), @@ -899,21 +948,24 @@ export class ZodSchemaFactory< } @cache() - private makeRelationSelectIncludeSchema(model: string, field: string) { + private makeRelationSelectIncludeSchema(model: string, field: string, options?: CreateSchemaOptions) { const fieldDef = requireField(this.schema, model, field); + const nextOpts = this.nextOptions(options); let objSchema: ZodType = z.strictObject({ ...(fieldDef.array || fieldDef.optional ? { // to-many relations and optional to-one relations are filterable - where: z.lazy(() => this.makeWhereSchema(fieldDef.type, false)).optional(), + where: z + .lazy(() => this.makeWhereSchema(fieldDef.type, false, false, false, nextOpts)) + .optional(), } : {}), select: z - .lazy(() => this.makeSelectSchema(fieldDef.type)) + .lazy(() => this.makeSelectSchema(fieldDef.type, nextOpts)) .optional() .nullable(), include: z - .lazy(() => this.makeIncludeSchema(fieldDef.type)) + .lazy(() => this.makeIncludeSchema(fieldDef.type, nextOpts)) .optional() .nullable(), omit: z @@ -924,11 +976,11 @@ export class ZodSchemaFactory< ? { // to-many relations can be ordered, skipped, taken, and cursor-located orderBy: z - .lazy(() => this.orArray(this.makeOrderBySchema(fieldDef.type, true, false), true)) + .lazy(() => this.orArray(this.makeOrderBySchema(fieldDef.type, true, false, nextOpts), true)) .optional(), skip: this.makeSkipSchema().optional(), take: this.makeTakeSchema().optional(), - cursor: this.makeCursorSchema(fieldDef.type).optional(), + cursor: this.makeCursorSchema(fieldDef.type, nextOpts).optional(), distinct: this.makeDistinctSchema(fieldDef.type).optional(), } : {}), @@ -960,39 +1012,45 @@ export class ZodSchemaFactory< } @cache() - private makeIncludeSchema(model: string) { + private makeIncludeSchema(model: string, options?: CreateSchemaOptions) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { + if (!this.shouldIncludeRelations(options)) { + continue; + } // Check if the target model is allowed by slicing configuration if (this.isModelAllowed(fieldDef.type)) { - fields[field] = this.makeRelationSelectIncludeSchema(model, field).optional(); + fields[field] = this.makeRelationSelectIncludeSchema(model, field, options).optional(); } } } - const _countSchema = this.makeCountSelectionSchema(model); - if (!(_countSchema instanceof z.ZodNever)) { - fields['_count'] = _countSchema; + if (this.shouldIncludeRelations(options)) { + const _countSchema = this.makeCountSelectionSchema(model, options); + if (!(_countSchema instanceof z.ZodNever)) { + fields['_count'] = _countSchema; + } } return z.strictObject(fields); } @cache() - private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean) { + private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean, options?: CreateSchemaOptions) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; const sort = z.union([z.literal('asc'), z.literal('desc')]); + const nextOpts = this.nextOptions(options); for (const field of Object.keys(modelDef.fields)) { const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { // relations - if (withRelation) { + if (withRelation && this.shouldIncludeRelations(options)) { fields[field] = z.lazy(() => { - let relationOrderBy = this.makeOrderBySchema(fieldDef.type, withRelation, WithAggregation); + let relationOrderBy = this.makeOrderBySchema(fieldDef.type, withRelation, WithAggregation, nextOpts); if (fieldDef.array) { relationOrderBy = relationOrderBy.extend({ _count: sort, @@ -1023,7 +1081,7 @@ export class ZodSchemaFactory< if (WithAggregation) { const aggregationFields = ['_count', '_avg', '_sum', '_min', '_max']; for (const agg of aggregationFields) { - fields[agg] = z.lazy(() => this.makeOrderBySchema(model, true, false).optional()); + fields[agg] = z.lazy(() => this.makeOrderBySchema(model, true, false, options).optional()); } } @@ -1037,9 +1095,9 @@ export class ZodSchemaFactory< return nonRelationFields.length > 0 ? this.orArray(z.enum(nonRelationFields as any), true) : z.never(); } - private makeCursorSchema(model: string) { + private makeCursorSchema(model: string, options?: CreateSchemaOptions) { // `makeWhereSchema` is already cached - return this.makeWhereSchema(model, true, true).optional(); + return this.makeWhereSchema(model, true, true, false, options).optional(); } // #endregion @@ -1049,12 +1107,13 @@ export class ZodSchemaFactory< @cache() makeCreateSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { - const dataSchema = this.makeCreateDataSchema(model, false); + const dataSchema = this.makeCreateDataSchema(model, false, [], false, options); const baseSchema = z.strictObject({ data: dataSchema, - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), + select: this.makeSelectSchema(model, options).optional().nullable(), + include: this.makeIncludeSchema(model, options).optional().nullable(), omit: this.makeOmitSchema(model).optional().nullable(), }); let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'create'); @@ -1066,9 +1125,10 @@ export class ZodSchemaFactory< @cache() makeCreateManySchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { return this.mergePluginArgsSchema( - this.makeCreateManyPayloadSchema(model, []), + this.makeCreateManyPayloadSchema(model, [], options), 'createMany', ) as unknown as ZodType>; } @@ -1076,10 +1136,11 @@ export class ZodSchemaFactory< @cache() makeCreateManyAndReturnSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { - const base = this.makeCreateManyPayloadSchema(model, []); + const base = this.makeCreateManyPayloadSchema(model, [], options); let result: ZodObject = base.extend({ - select: this.makeSelectSchema(model).optional().nullable(), + select: this.makeSelectSchema(model, options).optional().nullable(), omit: this.makeOmitSchema(model).optional().nullable(), }); result = this.mergePluginArgsSchema(result, 'createManyAndReturn'); @@ -1094,14 +1155,18 @@ export class ZodSchemaFactory< canBeArray: boolean, withoutFields: string[] = [], withoutRelationFields = false, + options?: CreateSchemaOptions, ) { + const skipRelations = withoutRelationFields || !this.shouldIncludeRelations(options); const uncheckedVariantFields: Record = {}; const checkedVariantFields: Record = {}; const modelDef = requireModel(this.schema, model); const hasRelation = - !withoutRelationFields && + !skipRelations && Object.entries(modelDef.fields).some(([f, def]) => !withoutFields.includes(f) && def.relation); + const nextOpts = this.nextOptions(options); + Object.keys(modelDef.fields).forEach((field) => { if (withoutFields.includes(field)) { return; @@ -1117,7 +1182,7 @@ export class ZodSchemaFactory< } if (fieldDef.relation) { - if (withoutRelationFields) { + if (skipRelations) { return; } // Check if the target model is allowed by slicing configuration @@ -1135,7 +1200,7 @@ export class ZodSchemaFactory< } let fieldSchema: ZodType = z.lazy(() => - this.makeRelationManipulationSchema(model, field, excludeFields, 'create'), + this.makeRelationManipulationSchema(model, field, excludeFields, 'create', nextOpts), ); if (fieldDef.optional || fieldDef.array) { @@ -1234,49 +1299,61 @@ export class ZodSchemaFactory< field: string, withoutFields: string[], mode: 'create' | 'update', + options?: CreateSchemaOptions, ) { const fieldDef = requireField(this.schema, model, field); const fieldType = fieldDef.type; const array = !!fieldDef.array; const fields: Record = { - create: this.makeCreateDataSchema(fieldDef.type, !!fieldDef.array, withoutFields).optional(), - - connect: this.makeConnectDataSchema(fieldType, array).optional(), - - connectOrCreate: this.makeConnectOrCreateDataSchema(fieldType, array, withoutFields).optional(), + create: this.makeCreateDataSchema( + fieldDef.type, + !!fieldDef.array, + withoutFields, + false, + options, + ).optional(), + + connect: this.makeConnectDataSchema(fieldType, array, options).optional(), + + connectOrCreate: this.makeConnectOrCreateDataSchema( + fieldType, + array, + withoutFields, + options, + ).optional(), }; if (array) { - fields['createMany'] = this.makeCreateManyPayloadSchema(fieldType, withoutFields).optional(); + fields['createMany'] = this.makeCreateManyPayloadSchema(fieldType, withoutFields, options).optional(); } if (mode === 'update') { if (fieldDef.optional || fieldDef.array) { // disconnect and delete are only available for optional/to-many relations - fields['disconnect'] = this.makeDisconnectDataSchema(fieldType, array).optional(); + fields['disconnect'] = this.makeDisconnectDataSchema(fieldType, array, options).optional(); - fields['delete'] = this.makeDeleteRelationDataSchema(fieldType, array, true).optional(); + fields['delete'] = this.makeDeleteRelationDataSchema(fieldType, array, true, options).optional(); } fields['update'] = array ? this.orArray( z.strictObject({ - where: this.makeWhereSchema(fieldType, true), - data: this.makeUpdateDataSchema(fieldType, withoutFields), + where: this.makeWhereSchema(fieldType, true, false, false, options), + data: this.makeUpdateDataSchema(fieldType, withoutFields, false, options), }), true, ).optional() : z .union([ z.strictObject({ - where: this.makeWhereSchema(fieldType, false).optional(), - data: this.makeUpdateDataSchema(fieldType, withoutFields), + where: this.makeWhereSchema(fieldType, false, false, false, options).optional(), + data: this.makeUpdateDataSchema(fieldType, withoutFields, false, options), }), - this.makeUpdateDataSchema(fieldType, withoutFields), + this.makeUpdateDataSchema(fieldType, withoutFields, false, options), ]) .optional(); - let upsertWhere = this.makeWhereSchema(fieldType, true); + let upsertWhere = this.makeWhereSchema(fieldType, true, false, false, options); if (!fieldDef.array) { // to-one relation, can upsert without where clause upsertWhere = upsertWhere.optional(); @@ -1284,25 +1361,30 @@ export class ZodSchemaFactory< fields['upsert'] = this.orArray( z.strictObject({ where: upsertWhere, - create: this.makeCreateDataSchema(fieldType, false, withoutFields), - update: this.makeUpdateDataSchema(fieldType, withoutFields), + create: this.makeCreateDataSchema(fieldType, false, withoutFields, false, options), + update: this.makeUpdateDataSchema(fieldType, withoutFields, false, options), }), true, ).optional(); if (array) { // to-many relation specifics - fields['set'] = this.makeSetDataSchema(fieldType, true).optional(); + fields['set'] = this.makeSetDataSchema(fieldType, true, options).optional(); fields['updateMany'] = this.orArray( z.strictObject({ - where: this.makeWhereSchema(fieldType, false, true), - data: this.makeUpdateDataSchema(fieldType, withoutFields), + where: this.makeWhereSchema(fieldType, false, true, false, options), + data: this.makeUpdateDataSchema(fieldType, withoutFields, false, options), }), true, ).optional(); - fields['deleteMany'] = this.makeDeleteRelationDataSchema(fieldType, true, false).optional(); + fields['deleteMany'] = this.makeDeleteRelationDataSchema( + fieldType, + true, + false, + options, + ).optional(); } } @@ -1310,38 +1392,54 @@ export class ZodSchemaFactory< } @cache() - private makeSetDataSchema(model: string, canBeArray: boolean) { - return this.orArray(this.makeWhereSchema(model, true), canBeArray); + private makeSetDataSchema(model: string, canBeArray: boolean, options?: CreateSchemaOptions) { + return this.orArray( + this.makeWhereSchema(model, true, false, false, options), + canBeArray, + ); } @cache() - private makeConnectDataSchema(model: string, canBeArray: boolean) { - return this.orArray(this.makeWhereSchema(model, true), canBeArray); + private makeConnectDataSchema(model: string, canBeArray: boolean, options?: CreateSchemaOptions) { + return this.orArray( + this.makeWhereSchema(model, true, false, false, options), + canBeArray, + ); } @cache() - private makeDisconnectDataSchema(model: string, canBeArray: boolean) { + private makeDisconnectDataSchema(model: string, canBeArray: boolean, options?: CreateSchemaOptions) { if (canBeArray) { // to-many relation, must be unique filters - return this.orArray(this.makeWhereSchema(model, true), canBeArray); + return this.orArray(this.makeWhereSchema(model, true, false, false, options), canBeArray); } else { // to-one relation, can be boolean or a regular filter - the entity // being disconnected is already uniquely identified by its parent - return z.union([z.boolean(), this.makeWhereSchema(model, false)]); + return z.union([z.boolean(), this.makeWhereSchema(model, false, false, false, options)]); } } @cache() - private makeDeleteRelationDataSchema(model: string, toManyRelation: boolean, uniqueFilter: boolean) { + private makeDeleteRelationDataSchema( + model: string, + toManyRelation: boolean, + uniqueFilter: boolean, + options?: CreateSchemaOptions, + ) { return toManyRelation - ? this.orArray(this.makeWhereSchema(model, uniqueFilter), true) - : z.union([z.boolean(), this.makeWhereSchema(model, uniqueFilter)]); + ? this.orArray(this.makeWhereSchema(model, uniqueFilter, false, false, options), true) + : z.union([z.boolean(), this.makeWhereSchema(model, uniqueFilter, false, false, options)]); } @cache() - private makeConnectOrCreateDataSchema(model: string, canBeArray: boolean, withoutFields: string[]) { - const whereSchema = this.makeWhereSchema(model, true); - const createSchema = this.makeCreateDataSchema(model, false, withoutFields); + private makeConnectOrCreateDataSchema( + model: string, + canBeArray: boolean, + withoutFields: string[], + options?: CreateSchemaOptions, + ) { + const whereSchema = this.makeWhereSchema(model, true, false, false, options); + const createSchema = this.makeCreateDataSchema(model, false, withoutFields, false, options); return this.orArray( z.strictObject({ where: whereSchema, @@ -1352,9 +1450,9 @@ export class ZodSchemaFactory< } @cache() - private makeCreateManyPayloadSchema(model: string, withoutFields: string[]) { + private makeCreateManyPayloadSchema(model: string, withoutFields: string[], options?: CreateSchemaOptions) { return z.strictObject({ - data: this.makeCreateDataSchema(model, true, withoutFields, true), + data: this.makeCreateDataSchema(model, true, withoutFields, true, options), skipDuplicates: z.boolean().optional(), }); } @@ -1366,12 +1464,13 @@ export class ZodSchemaFactory< @cache() makeUpdateSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, true), - data: this.makeUpdateDataSchema(model), - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), + where: this.makeWhereSchema(model, true, false, false, options), + data: this.makeUpdateDataSchema(model, [], false, options), + select: this.makeSelectSchema(model, options).optional().nullable(), + include: this.makeIncludeSchema(model, options).optional().nullable(), omit: this.makeOmitSchema(model).optional().nullable(), }); let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'update'); @@ -1383,11 +1482,12 @@ export class ZodSchemaFactory< @cache() makeUpdateManySchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { return this.mergePluginArgsSchema( z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - data: this.makeUpdateDataSchema(model, [], true), + where: this.makeWhereSchema(model, false, false, false, options).optional(), + data: this.makeUpdateDataSchema(model, [], true, options), limit: z.number().int().nonnegative().optional(), }), 'updateMany', @@ -1397,11 +1497,12 @@ export class ZodSchemaFactory< @cache() makeUpdateManyAndReturnSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { // plugin extended args schema is merged in `makeUpdateManySchema` - const baseSchema = this.makeUpdateManySchema(model) as unknown as ZodObject; + const baseSchema = this.makeUpdateManySchema(model, options) as unknown as ZodObject; let schema: ZodType = baseSchema.extend({ - select: this.makeSelectSchema(model).optional().nullable(), + select: this.makeSelectSchema(model, options).optional().nullable(), omit: this.makeOmitSchema(model).optional().nullable(), }); schema = this.refineForSelectOmitMutuallyExclusive(schema); @@ -1411,13 +1512,14 @@ export class ZodSchemaFactory< @cache() makeUpsertSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, true), - create: this.makeCreateDataSchema(model, false), - update: this.makeUpdateDataSchema(model), - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), + where: this.makeWhereSchema(model, true, false, false, options), + create: this.makeCreateDataSchema(model, false, [], false, options), + update: this.makeUpdateDataSchema(model, [], false, options), + select: this.makeSelectSchema(model, options).optional().nullable(), + include: this.makeIncludeSchema(model, options).optional().nullable(), omit: this.makeOmitSchema(model).optional().nullable(), }); let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'upsert'); @@ -1427,13 +1529,21 @@ export class ZodSchemaFactory< } @cache() - private makeUpdateDataSchema(model: string, withoutFields: string[] = [], withoutRelationFields = false) { + private makeUpdateDataSchema( + model: string, + withoutFields: string[] = [], + withoutRelationFields = false, + options?: CreateSchemaOptions, + ) { + const skipRelations = withoutRelationFields || !this.shouldIncludeRelations(options); const uncheckedVariantFields: Record = {}; const checkedVariantFields: Record = {}; const modelDef = requireModel(this.schema, model); - const hasRelation = Object.entries(modelDef.fields).some( - ([key, value]) => value.relation && !withoutFields.includes(key), - ); + const hasRelation = + !skipRelations && + Object.entries(modelDef.fields).some(([key, value]) => value.relation && !withoutFields.includes(key)); + + const nextOpts = this.nextOptions(options); Object.keys(modelDef.fields).forEach((field) => { if (withoutFields.includes(field)) { @@ -1442,7 +1552,7 @@ export class ZodSchemaFactory< const fieldDef = requireField(this.schema, model, field); if (fieldDef.relation) { - if (withoutRelationFields) { + if (skipRelations) { return; } // Check if the target model is allowed by slicing configuration @@ -1459,7 +1569,7 @@ export class ZodSchemaFactory< } } let fieldSchema: ZodType = z - .lazy(() => this.makeRelationManipulationSchema(model, field, excludeFields, 'update')) + .lazy(() => this.makeRelationManipulationSchema(model, field, excludeFields, 'update', nextOpts)) .optional(); // optional to-one relation can be null if (fieldDef.optional && !fieldDef.array) { @@ -1545,11 +1655,12 @@ export class ZodSchemaFactory< @cache() makeDeleteSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, true), - select: this.makeSelectSchema(model).optional().nullable(), - include: this.makeIncludeSchema(model).optional().nullable(), + where: this.makeWhereSchema(model, true, false, false, options), + select: this.makeSelectSchema(model, options).optional().nullable(), + include: this.makeIncludeSchema(model, options).optional().nullable(), omit: this.makeOmitSchema(model).optional().nullable(), }); let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'delete'); @@ -1561,10 +1672,11 @@ export class ZodSchemaFactory< @cache() makeDeleteManySchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType | undefined> { return this.mergePluginArgsSchema( z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), + where: this.makeWhereSchema(model, false, false, false, options).optional(), limit: z.number().int().nonnegative().optional(), }), 'deleteMany', @@ -1578,13 +1690,14 @@ export class ZodSchemaFactory< @cache() makeCountSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType | undefined> { return this.mergePluginArgsSchema( z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), + where: this.makeWhereSchema(model, false, false, false, options).optional(), skip: this.makeSkipSchema().optional(), take: this.makeTakeSchema().optional(), - orderBy: this.orArray(this.makeOrderBySchema(model, true, false), true).optional(), + orderBy: this.orArray(this.makeOrderBySchema(model, true, false, options), true).optional(), select: this.makeCountAggregateInputSchema(model).optional(), }), 'count', @@ -1616,13 +1729,14 @@ export class ZodSchemaFactory< @cache() makeAggregateSchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType | undefined> { return this.mergePluginArgsSchema( z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), + where: this.makeWhereSchema(model, false, false, false, options).optional(), skip: this.makeSkipSchema().optional(), take: this.makeTakeSchema().optional(), - orderBy: this.orArray(this.makeOrderBySchema(model, true, false), true).optional(), + orderBy: this.orArray(this.makeOrderBySchema(model, true, false, options), true).optional(), _count: this.makeCountAggregateInputSchema(model).optional(), _avg: this.makeSumAvgInputSchema(model).optional(), _sum: this.makeSumAvgInputSchema(model).optional(), @@ -1674,6 +1788,7 @@ export class ZodSchemaFactory< @cache() makeGroupBySchema>( model: Model, + options?: CreateSchemaOptions, ): ZodType> { const modelDef = requireModel(this.schema, model); const nonRelationFields = Object.keys(modelDef.fields).filter((field) => !modelDef.fields[field]?.relation); @@ -1683,10 +1798,10 @@ export class ZodSchemaFactory< : z.never(); const baseSchema = z.strictObject({ - where: this.makeWhereSchema(model, false).optional(), - orderBy: this.orArray(this.makeOrderBySchema(model, false, true), true).optional(), + where: this.makeWhereSchema(model, false, false, false, options).optional(), + orderBy: this.orArray(this.makeOrderBySchema(model, false, true, options), true).optional(), by: bySchema, - having: this.makeHavingSchema(model).optional(), + having: this.makeHavingSchema(model, options).optional(), skip: this.makeSkipSchema().optional(), take: this.makeTakeSchema().optional(), _count: this.makeCountAggregateInputSchema(model).optional(), @@ -1761,9 +1876,9 @@ export class ZodSchemaFactory< return true; } - private makeHavingSchema(model: string) { + private makeHavingSchema(model: string, options?: CreateSchemaOptions) { // `makeWhereSchema` is cached - return this.makeWhereSchema(model, false, true, true); + return this.makeWhereSchema(model, false, true, true, options); } // #endregion @@ -1771,7 +1886,10 @@ export class ZodSchemaFactory< // #region Procedures @cache() - makeProcedureParamSchema(param: { type: string; array?: boolean; optional?: boolean }): ZodType { + makeProcedureParamSchema( + param: { type: string; array?: boolean; optional?: boolean }, + _options?: CreateSchemaOptions, + ): ZodType { let schema: ZodType; if (isTypeDef(this.schema, param.type)) { diff --git a/packages/zod/src/index.ts b/packages/zod/src/index.ts index 667815bbe..55f241ea1 100644 --- a/packages/zod/src/index.ts +++ b/packages/zod/src/index.ts @@ -1,3 +1,2 @@ -export { createSchemaFactory as createModelSchemaFactory } from './factory'; -export type * from './types'; +export { createSchemaFactory } from './factory'; export * as ZodUtils from './utils'; diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 8ebf2aafc..3cd16af15 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -1,10 +1,10 @@ import Decimal from 'decimal.js'; import { describe, expect, expectTypeOf, it } from 'vitest'; -import { createModelSchemaFactory } from '../src/index'; +import { createSchemaFactory } from '../src/index'; import { schema } from './schema/schema'; import z from 'zod'; -const factory = createModelSchemaFactory(schema); +const factory = createSchemaFactory(schema); // A fully valid User object (without relations) const validUser = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f5a1e0575..a950c4bb5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -949,7 +949,7 @@ importers: version: 2.0.8 nuxt: specifier: 'catalog:' - version: 4.2.2(6ed21839bfb37f6518a04a21db8dd1a5) + version: 4.2.2(8d53318c89c53efd506d8dd328c0edc0) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -966,6 +966,9 @@ importers: '@zenstackhq/cli': specifier: workspace:* version: link:../../packages/cli + vue-tsc: + specifier: ^3.2.5 + version: 3.2.5(typescript@5.9.3) samples/orm: dependencies: @@ -4027,9 +4030,18 @@ packages: '@volar/language-core@2.4.27': resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + '@volar/source-map@2.4.27': resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + '@vue-macros/common@3.1.1': resolution: {integrity: sha512-afW2DMjgCBVs33mWRlz7YsGHzoEEupnl0DK5ZTKsgziAlLh5syc5m+GM7eqeYrgiQpwMaVxa1fk73caCvPxyAw==} engines: {node: '>=20.19.0'} @@ -4096,6 +4108,9 @@ packages: '@vue/language-core@3.2.1': resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==} + '@vue/language-core@3.2.5': + resolution: {integrity: sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==} + '@vue/reactivity@3.5.22': resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==} @@ -8688,6 +8703,12 @@ packages: peerDependencies: vue: ^3.5.0 + vue-tsc@3.2.5: + resolution: {integrity: sha512-/htfTCMluQ+P2FISGAooul8kO4JMheOTCbCy4M6dYnYYjqLe3BExZudAua6MSIKSFYQtFOYAll7XobYwcpokGA==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + vue@3.5.22: resolution: {integrity: sha512-toaZjQ3a/G/mYaLSbV+QsQhIdMo9x5rrqIpYRObsJ6T/J+RyCSFwN2LHNVH9v8uIcljDNa3QzPVdv3Y6b9hAJQ==} peerDependencies: @@ -10241,6 +10262,70 @@ snapshots: - uploadthing - xml2js + '@nuxt/nitro-server@4.2.2(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.8.2)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(typescript@5.9.3)': + dependencies: + '@nuxt/devalue': 2.0.2 + '@nuxt/kit': 4.2.2(magicast@0.5.1) + '@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 + consola: 3.4.2 + defu: 6.1.4 + destr: 2.0.5 + devalue: 5.6.1 + errx: 0.1.0 + escape-string-regexp: 5.0.0 + exsolve: 1.0.8 + h3: 1.15.4 + impound: 1.0.0 + klona: 2.0.6 + mocked-exports: 0.1.1 + nitropack: 2.12.9(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) + nuxt: 4.2.2(8d53318c89c53efd506d8dd328c0edc0) + pathe: 2.0.3 + pkg-types: 2.3.0 + radix3: 1.1.2 + std-env: 3.10.0 + ufo: 1.6.1 + unctx: 2.4.1 + unstorage: 1.17.3(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.8.2) + vue: 3.5.26(typescript@5.9.3) + vue-bundle-renderer: 2.2.0 + vue-devtools-stub: 0.1.0 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - bare-abort-controller + - better-sqlite3 + - db0 + - drizzle-orm + - encoding + - idb-keyval + - ioredis + - magicast + - mysql2 + - react-native-b4a + - rolldown + - sqlite3 + - supports-color + - typescript + - uploadthing + - xml2js + '@nuxt/schema@4.2.2': dependencies: '@vue/shared': 3.5.26 @@ -10297,7 +10382,66 @@ snapshots: unenv: 2.0.0-rc.24 vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) vite-node: 5.2.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)) + vue: 3.5.26(typescript@5.9.3) + vue-bundle-renderer: 2.2.0 + transitivePeerDependencies: + - '@biomejs/biome' + - '@types/node' + - eslint + - less + - lightningcss + - magicast + - meow + - optionator + - oxlint + - rollup + - sass + - sass-embedded + - stylelint + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - vls + - vti + - vue-tsc + - yaml + + '@nuxt/vite-builder@4.2.2(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)': + dependencies: + '@nuxt/kit': 4.2.2(magicast@0.5.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.52.5) + '@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': 5.1.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + autoprefixer: 10.4.23(postcss@8.5.6) + consola: 3.4.2 + cssnano: 7.1.2(postcss@8.5.6) + defu: 6.1.4 + esbuild: 0.27.2 + escape-string-regexp: 5.0.0 + exsolve: 1.0.8 + get-port-please: 3.2.0 + h3: 1.15.4 + jiti: 2.6.1 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.0 + mocked-exports: 0.1.1 + nuxt: 4.2.2(8d53318c89c53efd506d8dd328c0edc0) + pathe: 2.0.3 + pkg-types: 2.3.0 + postcss: 8.5.6 + rollup-plugin-visualizer: 6.0.5(rollup@4.52.5) + seroval: 1.4.1 + std-env: 3.10.0 + ufo: 1.6.1 + unenv: 2.0.0-rc.24 + vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-node: 5.2.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)) vue: 3.5.26(typescript@5.9.3) vue-bundle-renderer: 2.2.0 transitivePeerDependencies: @@ -11706,8 +11850,20 @@ snapshots: dependencies: '@volar/source-map': 2.4.27 + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + '@volar/source-map@2.4.27': {} + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + '@vue-macros/common@3.1.1(vue@3.5.26(typescript@5.9.3))': dependencies: '@vue/compiler-sfc': 3.5.26 @@ -11845,6 +12001,16 @@ snapshots: path-browserify: 1.0.1 picomatch: 4.0.3 + '@vue/language-core@3.2.5': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.26 + '@vue/shared': 3.5.26 + alien-signals: 3.0.3 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.3 + '@vue/reactivity@3.5.22': dependencies: '@vue/shared': 3.5.22 @@ -13073,7 +13239,7 @@ snapshots: eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.6.1)) @@ -13106,7 +13272,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -13121,7 +13287,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -14889,6 +15055,129 @@ snapshots: - xml2js - yaml + nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0): + dependencies: + '@dxup/nuxt': 0.2.2(magicast@0.5.1) + '@nuxt/cli': 3.31.3(cac@6.7.14)(magicast@0.5.1) + '@nuxt/devtools': 3.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + '@nuxt/kit': 4.2.2(magicast@0.5.1) + '@nuxt/nitro-server': 4.2.2(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.8.2)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(typescript@5.9.3) + '@nuxt/schema': 4.2.2 + '@nuxt/telemetry': 2.6.6(magicast@0.5.1) + '@nuxt/vite-builder': 4.2.2(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2) + '@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) + '@vue/shared': 3.5.26 + c12: 3.3.3(magicast@0.5.1) + chokidar: 5.0.0 + compatx: 0.2.0 + consola: 3.4.2 + cookie-es: 2.0.0 + defu: 6.1.4 + destr: 2.0.5 + devalue: 5.6.1 + errx: 0.1.0 + escape-string-regexp: 5.0.0 + exsolve: 1.0.8 + h3: 1.15.4 + hookable: 5.5.3 + ignore: 7.0.5 + impound: 1.0.0 + jiti: 2.6.1 + klona: 2.0.6 + knitwork: 1.3.0 + magic-string: 0.30.21 + mlly: 1.8.0 + nanotar: 0.2.0 + nypm: 0.6.2 + ofetch: 1.5.1 + ohash: 2.0.11 + on-change: 6.0.1 + oxc-minify: 0.102.0 + oxc-parser: 0.102.0 + oxc-transform: 0.102.0 + oxc-walker: 0.6.0(oxc-parser@0.102.0) + pathe: 2.0.3 + perfect-debounce: 2.0.0 + pkg-types: 2.3.0 + radix3: 1.1.2 + scule: 1.3.0 + semver: 7.7.3 + std-env: 3.10.0 + tinyglobby: 0.2.15 + ufo: 1.6.1 + ultrahtml: 1.6.0 + uncrypto: 0.1.3 + unctx: 2.4.1 + unimport: 5.5.0 + unplugin: 2.3.11 + unplugin-vue-router: 0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.22(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)) + untyped: 2.0.0 + vue: 3.5.26(typescript@5.9.3) + vue-router: 4.6.4(vue@3.5.26(typescript@5.9.3)) + optionalDependencies: + '@parcel/watcher': 2.5.1 + '@types/node': 20.19.24 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@biomejs/biome' + - '@capacitor/preferences' + - '@deno/kv' + - '@electric-sql/pglite' + - '@libsql/client' + - '@netlify/blobs' + - '@planetscale/database' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - '@vitejs/devtools' + - '@vue/compiler-sfc' + - aws4fetch + - bare-abort-controller + - better-sqlite3 + - bufferutil + - cac + - commander + - db0 + - drizzle-orm + - encoding + - eslint + - idb-keyval + - ioredis + - less + - lightningcss + - magicast + - meow + - mysql2 + - optionator + - oxlint + - react-native-b4a + - rolldown + - rollup + - sass + - sass-embedded + - sqlite3 + - stylelint + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - utf-8-validate + - vite + - vls + - vti + - vue-tsc + - xml2js + - yaml + nypm@0.6.2: dependencies: citty: 0.1.6 @@ -16671,6 +16960,31 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 + unplugin-vue-router@0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.22(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)): + dependencies: + '@babel/generator': 7.28.5 + '@vue-macros/common': 3.1.1(vue@3.5.26(typescript@5.9.3)) + '@vue/compiler-sfc': 3.5.26 + '@vue/language-core': 3.2.1 + ast-walker-scope: 0.8.3 + chokidar: 5.0.0 + json5: 2.2.3 + local-pkg: 1.1.2 + magic-string: 0.30.21 + mlly: 1.8.0 + muggle-string: 0.4.1 + pathe: 2.0.3 + picomatch: 4.0.3 + scule: 1.3.0 + tinyglobby: 0.2.15 + unplugin: 2.3.11 + unplugin-utils: 0.3.1 + yaml: 2.8.2 + optionalDependencies: + vue-router: 4.6.4(vue@3.5.22(typescript@5.9.3)) + transitivePeerDependencies: + - vue + unplugin-vue-router@0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)): dependencies: '@babel/generator': 7.28.5 @@ -16825,7 +17139,7 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): + vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)): dependencies: '@babel/code-frame': 7.27.1 chokidar: 4.0.3 @@ -16840,6 +17154,7 @@ snapshots: eslint: 9.29.0(jiti@2.6.1) optionator: 0.9.4 typescript: 5.9.3 + vue-tsc: 3.2.5(typescript@5.9.3) vite-plugin-inspect@11.3.3(@nuxt/kit@4.2.2(magicast@0.5.1))(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): dependencies: @@ -17032,6 +17347,12 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.26(typescript@5.9.3) + vue-tsc@3.2.5(typescript@5.9.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.2.5 + typescript: 5.9.3 + vue@3.5.22(typescript@5.9.3): dependencies: '@vue/compiler-dom': 3.5.22 diff --git a/samples/nuxt/package.json b/samples/nuxt/package.json index 4145dc0aa..2660310cd 100644 --- a/samples/nuxt/package.json +++ b/samples/nuxt/package.json @@ -6,15 +6,15 @@ "generate": "zen generate --lite", "db:init": "pnpm generate && zen db push && npx tsx zenstack/seed.ts", "dev": "nuxt dev --port 3302", - "build": "pnpm generate && nuxt build", + "build": "pnpm generate && nuxt typecheck", "preview": "nuxt preview", "postinstall": "nuxt prepare" }, "dependencies": { "@tailwindcss/vite": "^4.1.18", "@tanstack/vue-query": "catalog:", - "@zenstackhq/schema": "workspace:*", "@zenstackhq/orm": "workspace:*", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/server": "workspace:*", "@zenstackhq/tanstack-query": "workspace:*", "better-sqlite3": "catalog:", @@ -26,6 +26,7 @@ }, "devDependencies": { "@types/better-sqlite3": "catalog:", - "@zenstackhq/cli": "workspace:*" + "@zenstackhq/cli": "workspace:*", + "vue-tsc": "^3.2.5" } } diff --git a/tests/e2e/orm/client-api/zod.test.ts b/tests/e2e/orm/client-api/zod.test.ts index 377b68743..586782f80 100644 --- a/tests/e2e/orm/client-api/zod.test.ts +++ b/tests/e2e/orm/client-api/zod.test.ts @@ -1060,4 +1060,319 @@ describe('Zod schema factory test', () => { }); // #endregion + + // #region CreateSchemaOptions depth + + describe('CreateSchemaOptions depth', () => { + // --- Find schemas --- + + describe('makeFindManySchema with depth', () => { + it('relationDepth: 0 rejects relation where filters', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 0 }); + // scalar where still works + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + // relation filter rejected + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + }); + + it('relationDepth: 0 rejects relation in select', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 0 }); + // scalar select works + expect(s.safeParse({ select: { id: true, email: true } }).success).toBe(true); + // relation select rejected + expect(s.safeParse({ select: { posts: true } }).success).toBe(false); + }); + + it('relationDepth: 0 rejects relation in include', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 0 }); + // relation include rejected + expect(s.safeParse({ include: { posts: true } }).success).toBe(false); + }); + + it('relationDepth: 0 rejects relation in orderBy', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 0 }); + // scalar orderBy works + expect(s.safeParse({ orderBy: { email: 'asc' } }).success).toBe(true); + // relation orderBy rejected + expect(s.safeParse({ orderBy: { posts: { _count: 'desc' } } }).success).toBe(false); + }); + + it('relationDepth: 1 allows one level of relation nesting', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 1 }); + // relation where allowed + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(true); + // relation select allowed + expect(s.safeParse({ select: { id: true, posts: true } }).success).toBe(true); + // relation include allowed + expect(s.safeParse({ include: { posts: true } }).success).toBe(true); + }); + + it('relationDepth: 1 rejects two levels of relation nesting in where', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 1 }); + // User -> posts -> comments (2 levels) rejected + expect( + s.safeParse({ + where: { posts: { some: { comments: { some: { content: 'hi' } } } } }, + }).success, + ).toBe(false); + }); + + it('relationDepth: 1 rejects two levels of relation nesting in select', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 1 }); + // nested relation in select rejected - can select posts but posts cannot select comments + expect( + s.safeParse({ + select: { posts: { select: { comments: true } } }, + }).success, + ).toBe(false); + }); + + it('relationDepth: 2 allows two levels of relation nesting', () => { + const s = client.$zod.makeFindManySchema('User', { relationDepth: 2 }); + // User -> posts -> comments (2 levels) allowed + expect( + s.safeParse({ + where: { posts: { some: { comments: { some: { content: 'hi' } } } } }, + }).success, + ).toBe(true); + // nested relation in select allowed + expect( + s.safeParse({ + select: { posts: { select: { comments: true } } }, + }).success, + ).toBe(true); + }); + + it('no options behaves as unlimited depth', () => { + const s = client.$zod.makeFindManySchema('User'); + // deep nesting works + expect( + s.safeParse({ + where: { posts: { some: { comments: { some: { content: 'hi' } } } } }, + }).success, + ).toBe(true); + expect( + s.safeParse({ + select: { posts: { select: { comments: true } } }, + }).success, + ).toBe(true); + }); + }); + + describe('makeFindUniqueSchema with depth', () => { + it('relationDepth: 0 limits to scalar where', () => { + const s = client.$zod.makeFindUniqueSchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { id: 'abc' } }).success).toBe(true); + expect(s.safeParse({ where: { id: 'abc' }, select: { posts: true } }).success).toBe(false); + }); + }); + + describe('makeFindFirstSchema with depth', () => { + it('relationDepth: 0 limits to scalar fields', () => { + const s = client.$zod.makeFindFirstSchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + }); + }); + + // --- Exists schema --- + + describe('makeExistsSchema with depth', () => { + it('relationDepth: 0 rejects relation filters', () => { + const s = client.$zod.makeExistsSchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + }); + }); + + // --- Create schemas --- + + describe('makeCreateSchema with depth', () => { + it('relationDepth: 0 rejects nested relation create', () => { + const s = client.$zod.makeCreateSchema('User', { relationDepth: 0 }); + // scalar-only create works + expect(s.safeParse({ data: { email: 'u@test.com' } }).success).toBe(true); + // nested relation create rejected + expect( + s.safeParse({ + data: { + email: 'u@test.com', + posts: { create: { title: 'Post' } }, + }, + }).success, + ).toBe(false); + }); + + it('relationDepth: 1 allows one level of nested create', () => { + const s = client.$zod.makeCreateSchema('User', { relationDepth: 1 }); + // one level nested create allowed + expect( + s.safeParse({ + data: { + email: 'u@test.com', + posts: { create: { title: 'Post' } }, + }, + }).success, + ).toBe(true); + }); + + it('relationDepth: 1 rejects two levels of nested create', () => { + const s = client.$zod.makeCreateSchema('User', { relationDepth: 1 }); + // two levels nested create rejected (posts -> comments) + expect( + s.safeParse({ + data: { + email: 'u@test.com', + posts: { + create: { + title: 'Post', + comments: { create: { content: 'Comment' } }, + }, + }, + }, + }).success, + ).toBe(false); + }); + + it('relationDepth: 0 rejects relation in select', () => { + const s = client.$zod.makeCreateSchema('User', { relationDepth: 0 }); + expect( + s.safeParse({ + data: { email: 'u@test.com' }, + select: { posts: true }, + }).success, + ).toBe(false); + }); + }); + + describe('makeCreateManySchema with depth', () => { + it('relationDepth: 0 accepts scalar-only data', () => { + const s = client.$zod.makeCreateManySchema('User', { relationDepth: 0 }); + expect(s.safeParse({ data: [{ email: 'u@test.com' }] }).success).toBe(true); + }); + }); + + // --- Update schemas --- + + describe('makeUpdateSchema with depth', () => { + it('relationDepth: 0 rejects nested relation update', () => { + const s = client.$zod.makeUpdateSchema('User', { relationDepth: 0 }); + // scalar-only update works + expect(s.safeParse({ where: { id: 'abc' }, data: { name: 'New Name' } }).success).toBe(true); + // nested relation update rejected + expect( + s.safeParse({ + where: { id: 'abc' }, + data: { posts: { create: { title: 'Post' } } }, + }).success, + ).toBe(false); + }); + + it('relationDepth: 1 allows one level of nested update', () => { + const s = client.$zod.makeUpdateSchema('User', { relationDepth: 1 }); + expect( + s.safeParse({ + where: { id: 'abc' }, + data: { posts: { create: { title: 'Post' } } }, + }).success, + ).toBe(true); + }); + }); + + describe('makeUpsertSchema with depth', () => { + it('relationDepth: 0 rejects nested relations in create/update data', () => { + const s = client.$zod.makeUpsertSchema('User', { relationDepth: 0 }); + // scalar upsert works + expect( + s.safeParse({ + where: { id: 'abc' }, + create: { email: 'u@test.com' }, + update: { name: 'New' }, + }).success, + ).toBe(true); + // nested relation in create rejected + expect( + s.safeParse({ + where: { id: 'abc' }, + create: { email: 'u@test.com', posts: { create: { title: 'Post' } } }, + update: { name: 'New' }, + }).success, + ).toBe(false); + }); + }); + + // --- Delete schemas --- + + describe('makeDeleteSchema with depth', () => { + it('relationDepth: 0 rejects relation in select/include', () => { + const s = client.$zod.makeDeleteSchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { id: 'abc' } }).success).toBe(true); + expect(s.safeParse({ where: { id: 'abc' }, select: { posts: true } }).success).toBe(false); + expect(s.safeParse({ where: { id: 'abc' }, include: { posts: true } }).success).toBe(false); + }); + }); + + describe('makeDeleteManySchema with depth', () => { + it('relationDepth: 0 rejects relation where filters', () => { + const s = client.$zod.makeDeleteManySchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + }); + }); + + // --- Count / Aggregate / GroupBy --- + + describe('makeCountSchema with depth', () => { + it('relationDepth: 0 rejects relation where and orderBy', () => { + const s = client.$zod.makeCountSchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + }); + }); + + describe('makeAggregateSchema with depth', () => { + it('relationDepth: 0 rejects relation where', () => { + const s = client.$zod.makeAggregateSchema('User', { relationDepth: 0 }); + expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ where: { posts: { some: { published: true } } } }).success).toBe(false); + }); + }); + + describe('makeGroupBySchema with depth', () => { + it('relationDepth: 0 rejects relation where', () => { + const s = client.$zod.makeGroupBySchema('User', { relationDepth: 0 }); + expect(s.safeParse({ by: ['email'], where: { email: 'u@test.com' } }).success).toBe(true); + expect(s.safeParse({ by: ['email'], where: { posts: { some: { published: true } } } }).success).toBe( + false, + ); + }); + }); + + // --- Where schema directly --- + + describe('makeWhereSchema with depth', () => { + it('relationDepth: 0 produces scalar-only where', () => { + const s = client.$zod.makeWhereSchema('User', false, false, false, { relationDepth: 0 }); + expect(s.safeParse({ email: 'u@test.com' }).success).toBe(true); + expect(s.safeParse({ posts: { some: { published: true } } }).success).toBe(false); + }); + + it('relationDepth: 0 still allows logical operators', () => { + const s = client.$zod.makeWhereSchema('User', false, false, false, { relationDepth: 0 }); + expect(s.safeParse({ AND: [{ email: 'a' }, { name: 'b' }] }).success).toBe(true); + expect(s.safeParse({ OR: [{ email: 'a' }] }).success).toBe(true); + expect(s.safeParse({ NOT: { email: 'a' } }).success).toBe(true); + }); + + it('relationDepth: 1 allows relation filters but not nested relation filters', () => { + const s = client.$zod.makeWhereSchema('Post', false, false, false, { relationDepth: 1 }); + // Post -> author (1 level) allowed + expect(s.safeParse({ author: { email: 'u@test.com' } }).success).toBe(true); + // Post -> author -> posts (2 levels) rejected + expect(s.safeParse({ author: { posts: { some: { published: true } } } }).success).toBe(false); + }); + }); + }); + + // #endregion }); From 608133aa7d1156cb0590dfceb332bdbff2379633 Mon Sep 17 00:00:00 2001 From: Jiasheng Date: Sun, 22 Feb 2026 05:32:57 +0800 Subject: [PATCH 09/25] refactor(proxy): change database driver packages to optional peer dependency (#2388) --- packages/cli/package.json | 21 +++++++++++++--- packages/cli/src/actions/proxy.ts | 42 ++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ceb53e2fa..3b59ac456 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -42,7 +42,6 @@ "@zenstackhq/orm": "workspace:*", "@zenstackhq/sdk": "workspace:*", "@zenstackhq/server": "workspace:*", - "better-sqlite3": "catalog:", "chokidar": "^5.0.0", "colors": "1.4.0", "commander": "^8.3.0", @@ -53,10 +52,8 @@ "jiti": "^2.6.1", "langium": "catalog:", "mixpanel": "^0.18.1", - "mysql2": "catalog:", "ora": "^5.4.1", "package-manager-detector": "^1.3.0", - "pg": "catalog:", "prisma": "catalog:", "semver": "^7.7.2", "ts-pattern": "catalog:" @@ -74,7 +71,23 @@ "@zenstackhq/vitest-config": "workspace:*", "tmp": "catalog:" }, + "peerDependencies": { + "pg": "catalog:", + "better-sqlite3": "catalog:", + "mysql2": "catalog:" + }, + "peerDependenciesMeta": { + "pg": { + "optional": true + }, + "better-sqlite3": { + "optional": true + }, + "mysql2": { + "optional": true + } + }, "engines": { "node": ">=20" } -} +} \ No newline at end of file diff --git a/packages/cli/src/actions/proxy.ts b/packages/cli/src/actions/proxy.ts index 017df190f..8e0a7e362 100644 --- a/packages/cli/src/actions/proxy.ts +++ b/packages/cli/src/actions/proxy.ts @@ -13,14 +13,14 @@ import { PostgresDialect } from '@zenstackhq/orm/dialects/postgres'; import { SqliteDialect } from '@zenstackhq/orm/dialects/sqlite'; import { RPCApiHandler } from '@zenstackhq/server/api'; import { ZenStackMiddleware } from '@zenstackhq/server/express'; -import SQLite from 'better-sqlite3'; +import type BetterSqlite3 from 'better-sqlite3'; import colors from 'colors'; import cors from 'cors'; import express from 'express'; import { createJiti } from 'jiti'; -import { createPool as createMysqlPool } from 'mysql2'; +import type { createPool as MysqlCreatePool } from 'mysql2'; import path from 'node:path'; -import { Pool as PgPool } from 'pg'; +import type { Pool as PgPoolType } from 'pg'; import { CliError } from '../cli-error'; import { getVersion } from '../utils/version-utils'; import { getOutputPath, getSchemaFile, loadSchemaDocument } from './action-utils'; @@ -67,7 +67,7 @@ export async function run(options: Options) { const provider = getStringLiteral(dataSource?.fields.find((f) => f.name === 'provider')?.value)!; - const dialect = createDialect(provider, databaseUrl!, outputPath); + const dialect = await createDialect(provider, databaseUrl!, outputPath); const jiti = createJiti(import.meta.url); @@ -137,9 +137,17 @@ function redactDatabaseUrl(url: string): string { } } -function createDialect(provider: string, databaseUrl: string, outputPath: string) { +async function createDialect(provider: string, databaseUrl: string, outputPath: string) { switch (provider) { case 'sqlite': { + let SQLite: typeof BetterSqlite3; + try { + SQLite = (await import('better-sqlite3')).default; + } catch { + throw new CliError( + `Package "better-sqlite3" is required for SQLite support. Please install it with: npm install better-sqlite3`, + ); + } let resolvedUrl = databaseUrl.trim(); if (resolvedUrl.startsWith('file:')) { const filePath = resolvedUrl.substring('file:'.length); @@ -152,20 +160,36 @@ function createDialect(provider: string, databaseUrl: string, outputPath: string database: new SQLite(resolvedUrl), }); } - case 'postgresql': + case 'postgresql': { + let PgPool: typeof PgPoolType; + try { + PgPool = (await import('pg')).Pool; + } catch { + throw new CliError( + `Package "pg" is required for PostgreSQL support. Please install it with: npm install pg`, + ); + } console.log(colors.gray(`Connecting to PostgreSQL database at: ${redactDatabaseUrl(databaseUrl)}`)); return new PostgresDialect({ pool: new PgPool({ connectionString: databaseUrl, }), }); - - case 'mysql': + } + case 'mysql': { + let createMysqlPool: typeof MysqlCreatePool; + try { + createMysqlPool = (await import('mysql2')).createPool; + } catch { + throw new CliError( + `Package "mysql2" is required for MySQL support. Please install it with: npm install mysql2`, + ); + } console.log(colors.gray(`Connecting to MySQL database at: ${redactDatabaseUrl(databaseUrl)}`)); return new MysqlDialect({ pool: createMysqlPool(databaseUrl), }); - + } default: throw new CliError(`Unsupported database provider: ${provider}`); } From d49c39e260bcc1f1027ae0859505e28538bc31b4 Mon Sep 17 00:00:00 2001 From: Eugen Istoc Date: Sun, 22 Feb 2026 07:40:32 -0500 Subject: [PATCH 10/25] fix: enhance delegate model interaction with afterEntityMutation plugin support in child updates (#2369) --- .../orm/src/client/crud/operations/base.ts | 6 ++- .../entity-mutation-hooks.test.ts | 42 +++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index d696f82ff..0314bcf95 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -1228,8 +1228,10 @@ export abstract class BaseOperationHandler { ); // only fields not consumed by base update will be used for this model finalData = baseUpdateResult.remainingFields; - // base update may change entity ids, update the filter - combinedWhere = baseUpdateResult.baseEntity; + // make sure to include only the id fields from the base entity in the final filter + combinedWhere = baseUpdateResult.baseEntity + ? getIdValues(this.schema, modelDef.baseModel!, baseUpdateResult.baseEntity) + : baseUpdateResult.baseEntity; // update this entity with fields in updated base if (baseUpdateResult.baseEntity) { diff --git a/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts b/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts index eb8a25f16..ceb063fe8 100644 --- a/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts +++ b/tests/e2e/orm/plugin-infra/entity-mutation-hooks.test.ts @@ -692,3 +692,45 @@ describe('Entity mutation hooks tests', () => { }); }); }); + +describe('Entity mutation hooks - delegate model interaction', () => { + it('update on child model succeeds with afterEntityMutation plugin', async () => { + console.log('RUNNING!!!!'); + const client = await createTestClient( + ` +model Base { + id Int @id @default(autoincrement()) + orgId Int + type String + @@delegate(type) +} + +model Child extends Base { + childField Int +} +`, + ); + + const withPlugin = client.$use({ + id: 'test', + onEntityMutation: { + afterEntityMutation() {}, + }, + }); + + const created = await withPlugin.child.create({ + data: { orgId: 1, childField: 10 }, + }); + + const updated = await withPlugin.child.update({ + where: { id: created.id }, + data: { orgId: 2, childField: 20 }, + }); + + expect(updated).toBeTruthy(); + expect(updated.orgId).toBe(2); + expect(updated.childField).toBe(20); + + await client.$disconnect(); + }); +}); From f3a98505017a14270065da6d55e1505c976ec0d2 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Mon, 23 Feb 2026 22:21:26 -0500 Subject: [PATCH 11/25] fix: reject select with only false fields to prevent empty SELECT SQL (#2401) Co-authored-by: Claude Opus 4.6 --- packages/orm/src/client/zod/factory.ts | 66 +++++++++++++----------- tests/regression/test/issue-2344.test.ts | 43 +++++++++++++++ 2 files changed, 80 insertions(+), 29 deletions(-) create mode 100644 tests/regression/test/issue-2344.test.ts diff --git a/packages/orm/src/client/zod/factory.ts b/packages/orm/src/client/zod/factory.ts index 8ef8446ab..5a985c74c 100644 --- a/packages/orm/src/client/zod/factory.ts +++ b/packages/orm/src/client/zod/factory.ts @@ -216,10 +216,7 @@ export class ZodSchemaFactory< } else { fields['take'] = this.makeTakeSchema().optional(); } - fields['orderBy'] = this.orArray( - this.makeOrderBySchema(model, true, false, options), - true, - ).optional(); + fields['orderBy'] = this.orArray(this.makeOrderBySchema(model, true, false, options), true).optional(); fields['cursor'] = this.makeCursorSchema(model, options).optional(); fields['distinct'] = this.makeDistinctSchema(model).optional(); } @@ -228,6 +225,7 @@ export class ZodSchemaFactory< let result: ZodType = this.mergePluginArgsSchema(baseSchema, operation); result = this.refineForSelectIncludeMutuallyExclusive(result); result = this.refineForSelectOmitMutuallyExclusive(result); + result = this.refineForSelectHasTruthyField(result); if (!unique) { result = result.optional(); @@ -988,6 +986,7 @@ export class ZodSchemaFactory< objSchema = this.refineForSelectIncludeMutuallyExclusive(objSchema); objSchema = this.refineForSelectOmitMutuallyExclusive(objSchema); + objSchema = this.refineForSelectHasTruthyField(objSchema); return z.union([z.boolean(), objSchema]); } @@ -1039,7 +1038,12 @@ export class ZodSchemaFactory< } @cache() - private makeOrderBySchema(model: string, withRelation: boolean, WithAggregation: boolean, options?: CreateSchemaOptions) { + private makeOrderBySchema( + model: string, + withRelation: boolean, + WithAggregation: boolean, + options?: CreateSchemaOptions, + ) { const modelDef = requireModel(this.schema, model); const fields: Record = {}; const sort = z.union([z.literal('asc'), z.literal('desc')]); @@ -1050,7 +1054,12 @@ export class ZodSchemaFactory< // relations if (withRelation && this.shouldIncludeRelations(options)) { fields[field] = z.lazy(() => { - let relationOrderBy = this.makeOrderBySchema(fieldDef.type, withRelation, WithAggregation, nextOpts); + let relationOrderBy = this.makeOrderBySchema( + fieldDef.type, + withRelation, + WithAggregation, + nextOpts, + ); if (fieldDef.array) { relationOrderBy = relationOrderBy.extend({ _count: sort, @@ -1119,6 +1128,7 @@ export class ZodSchemaFactory< let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'create'); schema = this.refineForSelectIncludeMutuallyExclusive(schema); schema = this.refineForSelectOmitMutuallyExclusive(schema); + schema = this.refineForSelectHasTruthyField(schema); return schema as ZodType>; } @@ -1144,9 +1154,9 @@ export class ZodSchemaFactory< omit: this.makeOmitSchema(model).optional().nullable(), }); result = this.mergePluginArgsSchema(result, 'createManyAndReturn'); - return this.refineForSelectOmitMutuallyExclusive(result).optional() as ZodType< - CreateManyAndReturnArgs - >; + return this.refineForSelectHasTruthyField( + this.refineForSelectOmitMutuallyExclusive(result), + ).optional() as ZodType>; } @cache() @@ -1315,12 +1325,7 @@ export class ZodSchemaFactory< connect: this.makeConnectDataSchema(fieldType, array, options).optional(), - connectOrCreate: this.makeConnectOrCreateDataSchema( - fieldType, - array, - withoutFields, - options, - ).optional(), + connectOrCreate: this.makeConnectOrCreateDataSchema(fieldType, array, withoutFields, options).optional(), }; if (array) { @@ -1379,12 +1384,7 @@ export class ZodSchemaFactory< true, ).optional(); - fields['deleteMany'] = this.makeDeleteRelationDataSchema( - fieldType, - true, - false, - options, - ).optional(); + fields['deleteMany'] = this.makeDeleteRelationDataSchema(fieldType, true, false, options).optional(); } } @@ -1393,18 +1393,12 @@ export class ZodSchemaFactory< @cache() private makeSetDataSchema(model: string, canBeArray: boolean, options?: CreateSchemaOptions) { - return this.orArray( - this.makeWhereSchema(model, true, false, false, options), - canBeArray, - ); + return this.orArray(this.makeWhereSchema(model, true, false, false, options), canBeArray); } @cache() private makeConnectDataSchema(model: string, canBeArray: boolean, options?: CreateSchemaOptions) { - return this.orArray( - this.makeWhereSchema(model, true, false, false, options), - canBeArray, - ); + return this.orArray(this.makeWhereSchema(model, true, false, false, options), canBeArray); } @cache() @@ -1476,6 +1470,7 @@ export class ZodSchemaFactory< let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'update'); schema = this.refineForSelectIncludeMutuallyExclusive(schema); schema = this.refineForSelectOmitMutuallyExclusive(schema); + schema = this.refineForSelectHasTruthyField(schema); return schema as ZodType>; } @@ -1506,6 +1501,7 @@ export class ZodSchemaFactory< omit: this.makeOmitSchema(model).optional().nullable(), }); schema = this.refineForSelectOmitMutuallyExclusive(schema); + schema = this.refineForSelectHasTruthyField(schema); return schema as ZodType>; } @@ -1525,6 +1521,7 @@ export class ZodSchemaFactory< let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'upsert'); schema = this.refineForSelectIncludeMutuallyExclusive(schema); schema = this.refineForSelectOmitMutuallyExclusive(schema); + schema = this.refineForSelectHasTruthyField(schema); return schema as ZodType>; } @@ -1666,6 +1663,7 @@ export class ZodSchemaFactory< let schema: ZodType = this.mergePluginArgsSchema(baseSchema, 'delete'); schema = this.refineForSelectIncludeMutuallyExclusive(schema); schema = this.refineForSelectOmitMutuallyExclusive(schema); + schema = this.refineForSelectHasTruthyField(schema); return schema as ZodType>; } @@ -2030,6 +2028,16 @@ export class ZodSchemaFactory< ); } + private refineForSelectHasTruthyField(schema: ZodType) { + return schema.refine((value: any) => { + const select = value['select']; + if (!select || typeof select !== 'object') { + return true; + } + return Object.values(select).some((v) => v); + }, '"select" must have at least one truthy value'); + } + private nullableIf(schema: ZodType, nullable: boolean) { return nullable ? schema.nullable() : schema; } diff --git a/tests/regression/test/issue-2344.test.ts b/tests/regression/test/issue-2344.test.ts new file mode 100644 index 000000000..b3387df4c --- /dev/null +++ b/tests/regression/test/issue-2344.test.ts @@ -0,0 +1,43 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/2344 +describe('Regression for issue 2344', () => { + it('should reject select with only false fields', async () => { + const db = await createTestClient( + ` +model User { + id String @id @default(cuid()) + email String @unique + name String? +} + `, + ); + + await db.user.create({ + data: { email: 'user1@test.com', name: 'User1' }, + }); + + // select with only false fields should be rejected by validation + await expect( + db.user.findMany({ + select: { id: false }, + }), + ).rejects.toThrow(/"select" must have at least one truthy value/); + + // select with all fields false should also be rejected + await expect( + db.user.findFirst({ + select: { id: false, email: false, name: false }, + }), + ).rejects.toThrow(/"select" must have at least one truthy value/); + + // mix of true and false should still work + const r = await db.user.findFirst({ + select: { id: false, email: true }, + }); + expect(r).toBeTruthy(); + expect('id' in r!).toBeFalsy(); + expect(r!.email).toBe('user1@test.com'); + }); +}); From defb7074f4d13862e416dee74b400bc00ef544f0 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Mon, 23 Feb 2026 22:41:45 -0500 Subject: [PATCH 12/25] chore: add regression test for #2375 (#2400) --- tests/regression/test/issue-2375.test.ts | 78 ++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 tests/regression/test/issue-2375.test.ts diff --git a/tests/regression/test/issue-2375.test.ts b/tests/regression/test/issue-2375.test.ts new file mode 100644 index 000000000..766e3023e --- /dev/null +++ b/tests/regression/test/issue-2375.test.ts @@ -0,0 +1,78 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, it } from 'vitest'; + +describe('Regression for issue #2375', () => { + it('verifies the issue', async () => { + const db = await createTestClient( + ` +model TestRunResults { + id Int @id @default(autoincrement()) + testRunStepResults TestRunStepResults[] +} + +model TestRunStepResults { + id Int @id @default(autoincrement()) + testRunResultId Int + testRunResult TestRunResults @relation(fields: [testRunResultId], references: [id]) + stepId Int + step Steps @relation(fields: [stepId], references: [id], onDelete: Cascade) + sharedStepItemId Int? + sharedStepItem SharedStepItem? @relation(fields: [sharedStepItemId], references: [id], onDelete: SetNull) + statusId Int +} + +model Steps { + id Int @id @default(autoincrement()) + order Int @default(0) + testRunStepResults TestRunStepResults[] +} + +model SharedStepItem { + id Int @id @default(autoincrement()) + order Int + testRunStepResults TestRunStepResults[] +} + `, + { provider: 'postgresql' }, + ); + + await db.testRunResults.create({ + data: { + testRunStepResults: { + create: [ + { + step: { + create: { + order: 1, + }, + }, + sharedStepItem: { + create: { + order: 1, + }, + }, + statusId: 1, + }, + { + step: { + create: { + order: 2, + }, + }, + sharedStepItem: { + create: { + order: 2, + }, + }, + statusId: 1, + }, + ], + }, + }, + }); + + await db.testRunStepResults.findMany({ + orderBy: [{ step: { order: 'asc' } }, { sharedStepItem: { order: 'asc' } }], + }); + }); +}); From 89e3acbb3860b4aa2bee64337d4d58c5e63cc671 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Mon, 23 Feb 2026 22:52:17 -0500 Subject: [PATCH 13/25] fix: auto-add "views" preview feature to generated Prisma schema (#2376) (#2402) Co-authored-by: Claude Opus 4.6 --- .../sdk/src/prisma/prisma-schema-generator.ts | 17 +++++++- tests/regression/test/issue-2376.test.ts | 42 +++++++++++++++++++ 2 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 tests/regression/test/issue-2376.test.ts diff --git a/packages/sdk/src/prisma/prisma-schema-generator.ts b/packages/sdk/src/prisma/prisma-schema-generator.ts index 553658add..a524755da 100644 --- a/packages/sdk/src/prisma/prisma-schema-generator.ts +++ b/packages/sdk/src/prisma/prisma-schema-generator.ts @@ -176,10 +176,23 @@ export class PrismaSchemaGenerator { private generateDefaultGenerator(prisma: PrismaModel) { const gen = prisma.addGenerator('client', [{ name: 'provider', text: '"prisma-client-js"' }]); + + const previewFeatures: string[] = []; + const dataSource = this.zmodel.declarations.find(isDataSource); if (dataSource?.fields.some((f) => f.name === 'extensions')) { - // enable "postgresqlExtensions" preview feature - gen.fields.push({ name: 'previewFeatures', text: '["postgresqlExtensions"]' }); + previewFeatures.push('postgresqlExtensions'); + } + + if (this.zmodel.declarations.some((d) => isDataModel(d) && d.isView)) { + previewFeatures.push('views'); + } + + if (previewFeatures.length > 0) { + gen.fields.push({ + name: 'previewFeatures', + text: JSON.stringify(previewFeatures), + }); } } diff --git a/tests/regression/test/issue-2376.test.ts b/tests/regression/test/issue-2376.test.ts new file mode 100644 index 000000000..1f84f3526 --- /dev/null +++ b/tests/regression/test/issue-2376.test.ts @@ -0,0 +1,42 @@ +import { PrismaSchemaGenerator } from '@zenstackhq/sdk'; +import { loadSchema } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +// https://github.com/zenstackhq/zenstack/issues/2376 +describe('Regression for issue 2376', () => { + it('should include views preview feature when schema contains views', async () => { + const model = await loadSchema(` +datasource db { + provider = 'sqlite' + url = 'file:./test.db' +} + +model User { + id Int @id @default(autoincrement()) + email String @unique + name String? + posts Post[] +} + +model Post { + id Int @id @default(autoincrement()) + title String + author User? @relation(fields: [authorId], references: [id]) + authorId Int? +} + +view UserPostCount { + id Int @unique + name String + postCount Int +} + `); + + const generator = new PrismaSchemaGenerator(model); + const prismaSchema = await generator.generate(); + + // The generated Prisma schema should include previewFeatures with "views" + expect(prismaSchema).toContain('previewFeatures'); + expect(prismaSchema).toContain('views'); + }); +}); From f2c567b853c25fc9619fbaae7a3a89257490b57e Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Tue, 24 Feb 2026 11:24:47 -0500 Subject: [PATCH 14/25] fix(orm): _count is not included in select clause's typing when querying from a to-one relation (#2403) --- packages/orm/src/client/crud-types.ts | 2 +- tests/e2e/orm/client-api/find.test.ts | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/packages/orm/src/client/crud-types.ts b/packages/orm/src/client/crud-types.ts index 329c55b8e..6c335cf93 100644 --- a/packages/orm/src/client/crud-types.ts +++ b/packages/orm/src/client/crud-types.ts @@ -1215,7 +1215,7 @@ export type FindArgs< : {}) : {}) & (AllowFilter extends true ? FilterArgs : {}) & - SelectIncludeOmit; + SelectIncludeOmit; export type FindManyArgs< Schema extends SchemaDef, diff --git a/tests/e2e/orm/client-api/find.test.ts b/tests/e2e/orm/client-api/find.test.ts index 5b6ab5639..75f684de3 100644 --- a/tests/e2e/orm/client-api/find.test.ts +++ b/tests/e2e/orm/client-api/find.test.ts @@ -1120,6 +1120,27 @@ describe('Client find tests ', () => { ).resolves.toMatchObject({ _count: { posts: 0 }, }); + + // typing + // user select allows _count because it has to-many relations + client.user.findMany({ + select: { _count: { select: { posts: true } } }, + }); + // nested author select allows _count because it has to-many relations + client.post.findMany({ + select: { + author: { + select: { + _count: { select: { posts: true } }, + }, + }, + }, + }); + // comment select does not allow _count because it has no to-many relations + client.comment.findMany({ + // @ts-expect-error + select: { _count: {} }, + }); }); it('supports _count inside include', async () => { From 17922f03e7e343ffb1361fed30723bb21e99ab98 Mon Sep 17 00:00:00 2001 From: Jiasheng Date: Wed, 25 Feb 2026 20:49:43 +0800 Subject: [PATCH 15/25] feat(transaction): implement transaction handling with sequential operations and validation (#2399) Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- packages/server/src/api/rpc/index.ts | 99 +++++++++- packages/server/test/api/rpc.test.ts | 268 +++++++++++++++++++++++++++ 2 files changed, 366 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/rpc/index.ts b/packages/server/src/api/rpc/index.ts index 6f572e82d..7b3cbcb39 100644 --- a/packages/server/src/api/rpc/index.ts +++ b/packages/server/src/api/rpc/index.ts @@ -1,5 +1,5 @@ import { lowerCaseFirst, safeJSONStringify } from '@zenstackhq/common-helpers'; -import { ORMError, ORMErrorReason, type ClientContract } from '@zenstackhq/orm'; +import { CoreCrudOperations, ORMError, ORMErrorReason, type ClientContract } from '@zenstackhq/orm'; import type { SchemaDef } from '@zenstackhq/orm/schema'; import SuperJSON from 'superjson'; import { match } from 'ts-pattern'; @@ -11,6 +11,9 @@ import { loggerSchema } from '../common/schemas'; import { processSuperJsonRequestPayload, unmarshalQ } from '../common/utils'; import { log, registerCustomSerializers } from '../utils'; +const TRANSACTION_ROUTE_PREFIX = '$transaction' as const; +const VALID_OPS = new Set(CoreCrudOperations as unknown as string[]); + registerCustomSerializers(); /** @@ -71,6 +74,15 @@ export class RPCApiHandler implements ApiH }); } + if (model === TRANSACTION_ROUTE_PREFIX) { + return this.handleTransaction({ + client, + method: method.toUpperCase(), + type: op, + requestBody, + }); + } + model = lowerCaseFirst(model); method = method.toUpperCase(); let args: unknown; @@ -185,6 +197,91 @@ export class RPCApiHandler implements ApiH } } + private async handleTransaction({ + client, + method, + type, + requestBody, + }: { + client: ClientContract; + method: string; + type: string; + requestBody?: unknown; + }): Promise { + if (method !== 'POST') { + return this.makeBadInputErrorResponse('invalid request method, only POST is supported'); + } + + if (type !== 'sequential') { + return this.makeBadInputErrorResponse(`unsupported transaction type: ${type}`); + } + + if (!requestBody || !Array.isArray(requestBody) || requestBody.length === 0) { + return this.makeBadInputErrorResponse('request body must be a non-empty array of operations'); + } + + const processedOps: Array<{ model: string; op: string; args: unknown }> = []; + + for (let i = 0; i < requestBody.length; i++) { + const item = requestBody[i]; + if (!item || typeof item !== 'object') { + return this.makeBadInputErrorResponse(`operation at index ${i} must be an object`); + } + const { model: itemModel, op: itemOp, args: itemArgs } = item as any; + if (!itemModel || typeof itemModel !== 'string') { + return this.makeBadInputErrorResponse(`operation at index ${i} is missing a valid "model" field`); + } + if (!itemOp || typeof itemOp !== 'string') { + return this.makeBadInputErrorResponse(`operation at index ${i} is missing a valid "op" field`); + } + if (!VALID_OPS.has(itemOp)) { + return this.makeBadInputErrorResponse(`operation at index ${i} has invalid op: ${itemOp}`); + } + if (!this.isValidModel(client, lowerCaseFirst(itemModel))) { + return this.makeBadInputErrorResponse(`operation at index ${i} has unknown model: ${itemModel}`); + } + if (itemArgs !== undefined && itemArgs !== null && (typeof itemArgs !== 'object' || Array.isArray(itemArgs))) { + return this.makeBadInputErrorResponse(`operation at index ${i} has invalid "args" field`); + } + + const { result: processedArgs, error: argsError } = await this.processRequestPayload(itemArgs ?? {}); + if (argsError) { + return this.makeBadInputErrorResponse(`operation at index ${i}: ${argsError}`); + } + processedOps.push({ model: lowerCaseFirst(itemModel), op: itemOp, args: processedArgs }); + } + + try { + const promises = processedOps.map(({ model, op, args }) => { + return (client as any)[model][op](args); + }); + + log(this.options.log, 'debug', () => `handling "$transaction" request with ${promises.length} operations`); + + const clientResult = await client.$transaction(promises as any); + + const { json, meta } = SuperJSON.serialize(clientResult); + const responseBody: any = { data: json }; + if (meta) { + responseBody.meta = { serialization: meta }; + } + + const response = { status: 200, body: responseBody }; + log( + this.options.log, + 'debug', + () => `sending response for "$transaction" request: ${safeJSONStringify(response)}`, + ); + return response; + } catch (err) { + log(this.options.log, 'error', `error occurred when handling "$transaction" request`, err); + if (err instanceof ORMError) { + return this.makeORMErrorResponse(err); + } + return this.makeGenericErrorResponse(err); + } + } + private async handleProcedureRequest({ client, method, diff --git a/packages/server/test/api/rpc.test.ts b/packages/server/test/api/rpc.test.ts index 30818ae8a..d98fecaa0 100644 --- a/packages/server/test/api/rpc.test.ts +++ b/packages/server/test/api/rpc.test.ts @@ -763,6 +763,274 @@ procedure echoOverview(o: Overview): Overview expect(r.data.stringList).toEqual(['d', 'e', 'f']); }); + describe('transaction', () => { + it('runs sequential operations atomically', async () => { + const handleRequest = makeHandler(); + + // Clean up + await rawClient.post.deleteMany(); + await rawClient.user.deleteMany(); + + const r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [ + { + model: 'User', + op: 'create', + args: { data: { id: 'txuser1', email: 'txuser1@abc.com' } }, + }, + { + model: 'Post', + op: 'create', + args: { data: { id: 'txpost1', title: 'Tx Post', authorId: 'txuser1' } }, + }, + { + model: 'Post', + op: 'findMany', + args: { where: { authorId: 'txuser1' } }, + }, + ], + client: rawClient, + }); + + expect(r.status).toBe(200); + expect(Array.isArray(r.data)).toBe(true); + expect(r.data).toHaveLength(3); + expect(r.data[0]).toMatchObject({ id: 'txuser1', email: 'txuser1@abc.com' }); + expect(r.data[1]).toMatchObject({ id: 'txpost1', title: 'Tx Post' }); + expect(r.data[2]).toHaveLength(1); + expect(r.data[2][0]).toMatchObject({ id: 'txpost1' }); + + // Clean up + await rawClient.post.deleteMany(); + await rawClient.user.deleteMany(); + }); + + it('rejects non-POST methods', async () => { + const handleRequest = makeHandler(); + + const r = await handleRequest({ + method: 'get', + path: '/$transaction/sequential', + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/only POST is supported/i); + }); + + it('rejects missing or non-array body', async () => { + const handleRequest = makeHandler(); + + let r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/non-empty array/i); + + r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [], + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/non-empty array/i); + + r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: { model: 'User', op: 'findMany', args: {} }, + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/non-empty array/i); + }); + + it('rejects unknown model in operation', async () => { + const handleRequest = makeHandler(); + + const r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [{ model: 'Ghost', op: 'create', args: { data: {} } }], + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/unknown model/i); + }); + + it('rejects invalid op in operation', async () => { + const handleRequest = makeHandler(); + + const r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [{ model: 'User', op: 'dropTable', args: {} }], + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/invalid op/i); + }); + + it('rejects operation missing model or op field', async () => { + const handleRequest = makeHandler(); + + let r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [{ op: 'create', args: { data: {} } }], + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/"model"/i); + + r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [{ model: 'User', args: { data: {} } }], + client: rawClient, + }); + expect(r.status).toBe(400); + expect(r.error.message).toMatch(/"op"/i); + }); + + it('returns error for invalid args (non-existent field in where clause)', async () => { + const handleRequest = makeHandler(); + + // findMany with a non-existent field in where → ORM validation error + let r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [ + { + model: 'User', + op: 'findMany', + args: { where: { nonExistentField: 'value' } }, + }, + ], + client: rawClient, + }); + expect(r.status).toBe(422); + expect(r.error.message).toMatch(/validation error/i); + + // findUnique missing required where clause → ORM validation error + r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [ + { + model: 'Post', + op: 'findUnique', + args: {}, + }, + ], + client: rawClient, + }); + expect(r.status).toBe(422); + expect(r.error.message).toMatch(/validation error/i); + + // create with missing required field → ORM validation error + r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [ + { + model: 'Post', + op: 'create', + // title is required but omitted + args: { data: {} }, + }, + ], + client: rawClient, + }); + expect(r.status).toBe(422); + expect(r.error.message).toMatch(/validation error/i); + }); + + it('deserializes SuperJSON-encoded args per operation', async () => { + const handleRequest = makeHandler(); + + // Clean up + await rawClient.post.deleteMany(); + await rawClient.user.deleteMany(); + + // Serialize args containing a Date so they need SuperJSON deserialization + const publishedAt = new Date('2025-01-15T00:00:00.000Z'); + const serialized = SuperJSON.serialize({ + data: { id: 'txuser3', email: 'txuser3@abc.com' }, + }); + const serializedPost = SuperJSON.serialize({ + data: { id: 'txpost3', title: 'Dated Post', authorId: 'txuser3', publishedAt }, + }); + + const r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [ + { + model: 'User', + op: 'create', + args: { ...(serialized.json as any), meta: { serialization: serialized.meta } }, + }, + { + model: 'Post', + op: 'create', + args: { ...(serializedPost.json as any), meta: { serialization: serializedPost.meta } }, + }, + ], + client: rawClient, + }); + + expect(r.status).toBe(200); + expect(r.data).toHaveLength(2); + expect(r.data[0]).toMatchObject({ id: 'txuser3' }); + expect(r.data[1]).toMatchObject({ id: 'txpost3' }); + + // Verify the Date was stored correctly + const post = await (rawClient as any).post.findUnique({ where: { id: 'txpost3' } }); + expect(post?.publishedAt instanceof Date).toBe(true); + expect((post?.publishedAt as Date)?.toISOString()).toBe(publishedAt.toISOString()); + + // Clean up + await rawClient.post.deleteMany(); + await rawClient.user.deleteMany(); + }); + + it('rolls back all operations when one fails', async () => { + const handleRequest = makeHandler(); + + // Ensure no users before + await rawClient.user.deleteMany(); + + const r = await handleRequest({ + method: 'post', + path: '/$transaction/sequential', + requestBody: [ + { + model: 'User', + op: 'create', + args: { data: { id: 'txuser2', email: 'txuser2@abc.com' } }, + }, + // duplicate id will cause a DB error → whole tx rolls back + { + model: 'User', + op: 'create', + args: { data: { id: 'txuser2', email: 'txuser2@abc.com' } }, + }, + ], + client: rawClient, + }); + expect(r.status).toBeGreaterThanOrEqual(400); + + // User should not have been committed + const count = await rawClient.user.count(); + expect(count).toBe(0); + }); + }); + function makeHandler() { const handler = new RPCApiHandler({ schema: client.$schema }); return async (args: any) => { From ca8f43721ba1cdd30cdb6e2f1191995c402bd6ee Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 25 Feb 2026 12:41:17 -0500 Subject: [PATCH 16/25] fix(orm): use compact alias names when transforming ORM queries to Kysely (#2406) --- CLAUDE.md | 1 + .../src/client/crud/dialects/base-dialect.ts | 11 +- .../dialects/lateral-join-dialect-base.ts | 5 +- .../orm/src/client/crud/dialects/mysql.ts | 2 +- .../src/client/crud/dialects/postgresql.ts | 2 +- .../orm/src/client/crud/dialects/sqlite.ts | 13 ++- .../orm/src/client/crud/operations/base.ts | 10 +- .../client/executor/temp-alias-transformer.ts | 27 +++++ .../executor/zenstack-query-executor.ts | 19 +++- packages/orm/src/client/options.ts | 6 + packages/orm/src/client/query-utils.ts | 13 +++ .../policy/src/expression-transformer.ts | 4 +- packages/plugins/policy/src/policy-handler.ts | 4 +- tests/regression/test/issue-2378.test.ts | 104 ++++++++++++++++++ 14 files changed, 195 insertions(+), 26 deletions(-) create mode 100644 packages/orm/src/client/executor/temp-alias-transformer.ts create mode 100644 tests/regression/test/issue-2378.test.ts diff --git a/CLAUDE.md b/CLAUDE.md index 6f74cd60a..04e18dee4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -20,6 +20,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ### Testing - E2E tests are in `tests/e2e/` directory +- Regression tests for GitHub issues go in `tests/regression/test/` as `issue-{number}.test.ts` ### ZenStack CLI Commands diff --git a/packages/orm/src/client/crud/dialects/base-dialect.ts b/packages/orm/src/client/crud/dialects/base-dialect.ts index 0f204817f..3ab2d4979 100644 --- a/packages/orm/src/client/crud/dialects/base-dialect.ts +++ b/packages/orm/src/client/crud/dialects/base-dialect.ts @@ -34,6 +34,7 @@ import { requireIdFields, requireModel, requireTypeDef, + tmpAlias, } from '../../query-utils'; export abstract class BaseCrudDialect { @@ -298,7 +299,7 @@ export abstract class BaseCrudDialect { } } - const joinAlias = `${modelAlias}$${field}`; + const joinAlias = tmpAlias(`${modelAlias}$${field}`); const joinPairs = buildJoinPairs( this.schema, model, @@ -307,7 +308,7 @@ export abstract class BaseCrudDialect { field, joinAlias, ); - const filterResultField = `${field}$filter`; + const filterResultField = tmpAlias(`${field}$flt`); const joinSelect = this.eb .selectFrom(`${fieldDef.type} as ${joinAlias}`) @@ -383,7 +384,7 @@ export abstract class BaseCrudDialect { // evaluating the filter involves creating an inner select, // give it an alias to avoid conflict - const relationFilterSelectAlias = `${modelAlias}$${field}$filter`; + const relationFilterSelectAlias = tmpAlias(`${modelAlias}$${field}$flt`); const buildPkFkWhereRefs = (eb: ExpressionBuilder) => { const m2m = getManyToManyRelation(this.schema, model, field); @@ -1083,7 +1084,7 @@ export abstract class BaseCrudDialect { ); const sort = this.negateSort(value._count, negated); result = result.orderBy((eb) => { - const subQueryAlias = `${modelAlias}$orderBy$${field}$count`; + const subQueryAlias = tmpAlias(`${modelAlias}$ob$${field}$ct`); let subQuery = this.buildSelectModel(relationModel, subQueryAlias); const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, subQueryAlias); subQuery = subQuery.where(() => @@ -1099,7 +1100,7 @@ export abstract class BaseCrudDialect { } } else { // order by to-one relation - const joinAlias = `${modelAlias}$orderBy$${index}`; + const joinAlias = tmpAlias(`${modelAlias}$ob$${index}`); result = result.leftJoin(`${relationModel} as ${joinAlias}`, (join) => { const joinPairs = buildJoinPairs(this.schema, model, modelAlias, field, joinAlias); return join.on((eb) => diff --git a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts index 65f2d9cec..94f29b20a 100644 --- a/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts +++ b/packages/orm/src/client/crud/dialects/lateral-join-dialect-base.ts @@ -11,6 +11,7 @@ import { requireField, requireIdFields, requireModel, + tmpAlias, } from '../../query-utils'; import { BaseCrudDialect } from './base-dialect'; @@ -31,7 +32,7 @@ export abstract class LateralJoinDialectBase extends B parentAlias: string, payload: true | FindArgs, any, true>, ): SelectQueryBuilder { - const relationResultName = `${parentAlias}$${relationField}`; + const relationResultName = tmpAlias(`${parentAlias}$${relationField}`); const joinedQuery = this.buildRelationJSON( model, query, @@ -56,7 +57,7 @@ export abstract class LateralJoinDialectBase extends B return qb.leftJoinLateral( (eb) => { - const relationSelectName = `${resultName}$sub`; + const relationSelectName = tmpAlias(`${resultName}$sub`); const relationModelDef = requireModel(this.schema, relationModel); let tbl: SelectQueryBuilder; diff --git a/packages/orm/src/client/crud/dialects/mysql.ts b/packages/orm/src/client/crud/dialects/mysql.ts index 45e4aaea1..e9ca2e4aa 100644 --- a/packages/orm/src/client/crud/dialects/mysql.ts +++ b/packages/orm/src/client/crud/dialects/mysql.ts @@ -306,7 +306,7 @@ export class MySqlCrudDialect extends LateralJoinDiale return this.eb.exists( this.eb .selectFrom(sql`JSON_TABLE(${receiver}, '$[*]' COLUMNS(value JSON PATH '$'))`.as('$items')) - .select(this.eb.lit(1).as('$t')) + .select(this.eb.lit(1).as('_')) .where(buildFilter(this.eb.ref('$items.value'))), ); } diff --git a/packages/orm/src/client/crud/dialects/postgresql.ts b/packages/orm/src/client/crud/dialects/postgresql.ts index 62b19d3d6..5f962dbb5 100644 --- a/packages/orm/src/client/crud/dialects/postgresql.ts +++ b/packages/orm/src/client/crud/dialects/postgresql.ts @@ -352,7 +352,7 @@ export class PostgresCrudDialect extends LateralJoinDi return this.eb.exists( this.eb .selectFrom(this.eb.fn('jsonb_array_elements', [receiver]).as('$items')) - .select(this.eb.lit(1).as('$t')) + .select(this.eb.lit(1).as('_')) .where(buildFilter(this.eb.ref('$items.value'))), ); } diff --git a/packages/orm/src/client/crud/dialects/sqlite.ts b/packages/orm/src/client/crud/dialects/sqlite.ts index 93d4f547d..95e2910f8 100644 --- a/packages/orm/src/client/crud/dialects/sqlite.ts +++ b/packages/orm/src/client/crud/dialects/sqlite.ts @@ -25,6 +25,7 @@ import { requireField, requireIdFields, requireModel, + tmpAlias, } from '../../query-utils'; import { BaseCrudDialect } from './base-dialect'; @@ -201,7 +202,7 @@ export class SqliteCrudDialect extends BaseCrudDialect const relationModel = relationFieldDef.type as GetModels; const relationModelDef = requireModel(this.schema, relationModel); - const subQueryName = `${parentAlias}$${relationField}`; + const subQueryName = tmpAlias(`${parentAlias}$${relationField}`); let tbl: SelectQueryBuilder; if (this.canJoinWithoutNestedSelect(relationModelDef, payload)) { @@ -214,7 +215,7 @@ export class SqliteCrudDialect extends BaseCrudDialect // need to make a nested select on relation model tbl = eb.selectFrom(() => { // nested query name - const selectModelAlias = `${parentAlias}$${relationField}$sub`; + const selectModelAlias = tmpAlias(`${parentAlias}$${relationField}$sub`); // select all fields let selectModelQuery = this.buildModelSelect(relationModel, selectModelAlias, payload, true); @@ -268,7 +269,7 @@ export class SqliteCrudDialect extends BaseCrudDialect const subJson = this.buildCountJson( relationModel, eb, - `${parentAlias}$${relationField}`, + tmpAlias(`${parentAlias}$${relationField}`), value, ); return [sql.lit(field), subJson]; @@ -279,7 +280,7 @@ export class SqliteCrudDialect extends BaseCrudDialect relationModel, eb, field, - `${parentAlias}$${relationField}`, + tmpAlias(`${parentAlias}$${relationField}`), value, ); return [sql.lit(field), subJson]; @@ -305,7 +306,7 @@ export class SqliteCrudDialect extends BaseCrudDialect relationModel, eb, field, - `${parentAlias}$${relationField}`, + tmpAlias(`${parentAlias}$${relationField}`), value, ); return [sql.lit(field), subJson]; @@ -440,7 +441,7 @@ export class SqliteCrudDialect extends BaseCrudDialect return this.eb.exists( this.eb .selectFrom(this.eb.fn('json_each', [receiver]).as('$items')) - .select(this.eb.lit(1).as('$t')) + .select(this.eb.lit(1).as('_')) .where(buildFilter(this.eb.ref('$items.value'))), ); } diff --git a/packages/orm/src/client/crud/operations/base.ts b/packages/orm/src/client/crud/operations/base.ts index 0314bcf95..e38ca4369 100644 --- a/packages/orm/src/client/crud/operations/base.ts +++ b/packages/orm/src/client/crud/operations/base.ts @@ -260,23 +260,23 @@ export abstract class BaseOperationHandler { .exists( this.dialect .buildSelectModel(model, model) - .select(sql.lit(1).as('$t')) + .select(sql.lit(1).as('_')) .where(() => this.dialect.buildFilter(model, model, filter)), ) - .as('exists'), + .as('$exists'), ) .modifyEnd(this.makeContextComment({ model, operation: 'read' })); - let result: { exists: number | boolean }[] = []; + let result: { $exists: number | boolean }[] = []; const compiled = kysely.getExecutor().compileQuery(query.toOperationNode(), createQueryId()); try { const r = await kysely.getExecutor().executeQuery(compiled); - result = r.rows as { exists: number | boolean }[]; + result = r.rows as { $exists: number | boolean }[]; } catch (err) { throw createDBQueryError(`Failed to execute query: ${err}`, err, compiled.sql, compiled.parameters); } - return !!result[0]?.exists; + return !!result[0]?.$exists; } protected async read( diff --git a/packages/orm/src/client/executor/temp-alias-transformer.ts b/packages/orm/src/client/executor/temp-alias-transformer.ts new file mode 100644 index 000000000..1a66a260a --- /dev/null +++ b/packages/orm/src/client/executor/temp-alias-transformer.ts @@ -0,0 +1,27 @@ +import { IdentifierNode, OperationNodeTransformer, type OperationNode, type QueryId } from 'kysely'; +import { TEMP_ALIAS_PREFIX } from '../query-utils'; + +/** + * Kysely node transformer that replaces temporary aliases created during query construction with + * shorter names while ensuring the same temp alias gets replaced with the same name. + */ +export class TempAliasTransformer extends OperationNodeTransformer { + private aliasMap = new Map(); + + run(node: T): T { + this.aliasMap.clear(); + return this.transformNode(node); + } + + protected override transformIdentifier(node: IdentifierNode, queryId?: QueryId): IdentifierNode { + if (node.name.startsWith(TEMP_ALIAS_PREFIX)) { + let mapped = this.aliasMap.get(node.name); + if (!mapped) { + mapped = `$$t${this.aliasMap.size + 1}`; + this.aliasMap.set(node.name, mapped); + } + return IdentifierNode.create(mapped); + } + return super.transformIdentifier(node, queryId); + } +} diff --git a/packages/orm/src/client/executor/zenstack-query-executor.ts b/packages/orm/src/client/executor/zenstack-query-executor.ts index 4fa8b1896..b0aab7627 100644 --- a/packages/orm/src/client/executor/zenstack-query-executor.ts +++ b/packages/orm/src/client/executor/zenstack-query-executor.ts @@ -39,6 +39,7 @@ import { createDBQueryError, createInternalError, ORMError } from '../errors'; import type { AfterEntityMutationCallback, OnKyselyQueryCallback } from '../plugin'; import { requireIdFields, stripAlias } from '../query-utils'; import { QueryNameMapper } from './name-mapper'; +import { TempAliasTransformer } from './temp-alias-transformer'; import type { ZenStackDriver } from './zenstack-driver'; type MutationQueryNode = InsertQueryNode | UpdateQueryNode | DeleteQueryNode; @@ -620,10 +621,24 @@ In such cases, ZenStack cannot reliably determine the IDs of the mutated entitie }) as string; } + private processQueryNode(query: Node): Node { + let result = query; + result = this.processNameMapping(result); + result = this.processTempAlias(result); + return result; + } + private processNameMapping(query: Node): Node { return this.nameMapper?.transformNode(query) ?? query; } + private processTempAlias(query: Node): Node { + if (this.options.useCompactAliasNames === false) { + return query; + } + return new TempAliasTransformer().run(query); + } + private createClientForConnection(connection: DatabaseConnection, inTx: boolean) { const innerExecutor = this.withConnectionProvider(new SingleConnectionProvider(connection)); innerExecutor.suppressMutationHooks = true; @@ -650,8 +665,8 @@ In such cases, ZenStack cannot reliably determine the IDs of the mutated entitie queryId?: QueryId, parameters?: readonly unknown[], ) { - // no need to handle mutation hooks, just proceed - const finalQuery = this.processNameMapping(query); + // run query node processors: name mapping, temp alias renaming, etc. + const finalQuery = this.processQueryNode(query); // inherit the original queryId let compiledQuery = this.compileQuery(finalQuery, queryId ?? createQueryId()); diff --git a/packages/orm/src/client/options.ts b/packages/orm/src/client/options.ts index 2061ebafa..4601598b2 100644 --- a/packages/orm/src/client/options.ts +++ b/packages/orm/src/client/options.ts @@ -197,6 +197,12 @@ export type ClientOptions = QueryOptions & { * `@@validate`, etc. Defaults to `true`. */ validateInput?: boolean; + + /** + * Whether to use compact alias names (e.g., "$t1", "$t2") when transforming ORM queries to SQL. + * Defaults to `true`. + */ + useCompactAliasNames?: boolean; } & (HasComputedFields extends true ? { /** diff --git a/packages/orm/src/client/query-utils.ts b/packages/orm/src/client/query-utils.ts index fb9c39bea..0ea7ea58a 100644 --- a/packages/orm/src/client/query-utils.ts +++ b/packages/orm/src/client/query-utils.ts @@ -427,3 +427,16 @@ export function extractFieldName(node: OperationNode) { return undefined; } } + +export const TEMP_ALIAS_PREFIX = '$$_'; + +/** + * Create an alias name for a temporary table or column name. + */ +export function tmpAlias(name: string) { + if (!name.startsWith(TEMP_ALIAS_PREFIX)) { + return `${TEMP_ALIAS_PREFIX}${name}`; + } else { + return name; + } +} diff --git a/packages/plugins/policy/src/expression-transformer.ts b/packages/plugins/policy/src/expression-transformer.ts index 3b31bd05c..2a237c5cb 100644 --- a/packages/plugins/policy/src/expression-transformer.ts +++ b/packages/plugins/policy/src/expression-transformer.ts @@ -403,7 +403,7 @@ export class ExpressionTransformer { return this.transform(expr.left, { ...context, - memberSelect: SelectionNode.create(AliasNode.create(predicateResult, IdentifierNode.create('$t'))), + memberSelect: SelectionNode.create(AliasNode.create(predicateResult, IdentifierNode.create('_'))), memberFilter: predicateFilter, }); } @@ -776,7 +776,7 @@ export class ExpressionTransformer { return { ...receiver, - selections: [SelectionNode.create(AliasNode.create(currNode!, IdentifierNode.create('$t')))], + selections: [SelectionNode.create(AliasNode.create(currNode!, IdentifierNode.create('_')))], }; } diff --git a/packages/plugins/policy/src/policy-handler.ts b/packages/plugins/policy/src/policy-handler.ts index 25545cfc8..aa46be502 100644 --- a/packages/plugins/policy/src/policy-handler.ts +++ b/packages/plugins/policy/src/policy-handler.ts @@ -825,13 +825,13 @@ export class PolicyHandler extends OperationNodeTransf const queryA = eb .selectFrom(m2m.firstModel) .where(eb(eb.ref(`${m2m.firstModel}.${m2m.firstIdField}`), '=', aValue)) - .select(() => new ExpressionWrapper(filterA).as('$t')); + .select(() => new ExpressionWrapper(filterA).as('_')); const filterB = this.buildPolicyFilter(m2m.secondModel, undefined, 'update'); const queryB = eb .selectFrom(m2m.secondModel) .where(eb(eb.ref(`${m2m.secondModel}.${m2m.secondIdField}`), '=', bValue)) - .select(() => new ExpressionWrapper(filterB).as('$t')); + .select(() => new ExpressionWrapper(filterB).as('_')); // select both conditions in one query const queryNode: SelectQueryNode = { diff --git a/tests/regression/test/issue-2378.test.ts b/tests/regression/test/issue-2378.test.ts new file mode 100644 index 000000000..2bafd992f --- /dev/null +++ b/tests/regression/test/issue-2378.test.ts @@ -0,0 +1,104 @@ +import { createTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue #2378', () => { + it('deep nested include should not generate alias names exceeding 63 bytes', async () => { + const db = await createTestClient( + ` +model RepositoryCases { + id Int @id @default(autoincrement()) + templateId Int + template Templates @relation(fields: [templateId], references: [id]) +} + +model Templates { + id Int @id @default(autoincrement()) + cases RepositoryCases[] + caseFields TemplateCaseAssignment[] +} + +model TemplateCaseAssignment { + id Int @id @default(autoincrement()) + templateId Int + template Templates @relation(fields: [templateId], references: [id]) + caseFieldId Int + caseField CaseFields @relation(fields: [caseFieldId], references: [id]) +} + +model CaseFields { + id Int @id @default(autoincrement()) + assignments TemplateCaseAssignment[] + fieldOptions CaseFieldAssignment[] +} + +model CaseFieldAssignment { + id Int @id @default(autoincrement()) + caseFieldId Int + caseField CaseFields @relation(fields: [caseFieldId], references: [id]) + fieldOptionId Int + fieldOption FieldOptions @relation(fields: [fieldOptionId], references: [id]) +} + +model FieldOptions { + id Int @id @default(autoincrement()) + value String + assignments CaseFieldAssignment[] +} + `, + ); + + // seed data: RepositoryCases -> Templates -> TemplateCaseAssignment -> CaseFields -> CaseFieldAssignment -> FieldOptions + await db.repositoryCases.create({ + data: { + template: { + create: { + caseFields: { + create: { + caseField: { + create: { + fieldOptions: { + create: { + fieldOption: { + create: { value: 'option1' }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + // 5-level deep include that previously generated aliases exceeding 63 bytes + const result = await db.repositoryCases.findFirst({ + where: { id: 1 }, + include: { + template: { + include: { + caseFields: { + include: { + caseField: { + include: { + fieldOptions: { + include: { + fieldOption: true, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }); + + expect(result).toBeTruthy(); + expect(result.template.caseFields).toHaveLength(1); + expect(result.template.caseFields[0].caseField.fieldOptions).toHaveLength(1); + expect(result.template.caseFields[0].caseField.fieldOptions[0].fieldOption.value).toBe('option1'); + }); +}); From c7c3d86199e9cdb772a0bd5f7118cbc33289c73e Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Wed, 25 Feb 2026 20:47:09 -0500 Subject: [PATCH 17/25] chore: fix npm audit issues (#2407) --- package.json | 7 +- .../auth-adapters/better-auth/package.json | 6 +- packages/server/package.json | 6 +- pnpm-lock.yaml | 3669 ++++++++--------- pnpm-workspace.yaml | 10 +- 5 files changed, 1782 insertions(+), 1916 deletions(-) diff --git a/package.json b/package.json index 1bcc178bb..16a9ce13d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,11 @@ "better-sqlite3", "esbuild", "vue-demi" - ] + ], + "overrides": { + "cookie@<0.7.0": ">=0.7.0", + "lodash-es@>=4.0.0 <=4.17.22": ">=4.17.23", + "lodash@>=4.0.0 <=4.17.22": ">=4.17.23" + } } } diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json index 9be352a67..e19c325c4 100644 --- a/packages/auth-adapters/better-auth/package.json +++ b/packages/auth-adapters/better-auth/package.json @@ -46,9 +46,9 @@ "better-auth": "^1.3.0" }, "devDependencies": { - "@better-auth/core": "1.4.17", - "better-auth": "1.4.17", - "@better-auth/cli": "1.4.17", + "@better-auth/core": "1.4.19", + "better-auth": "1.4.19", + "@better-auth/cli": "1.4.19", "@types/tmp": "catalog:", "@zenstackhq/cli": "workspace:*", "@zenstackhq/eslint-config": "workspace:*", diff --git a/packages/server/package.json b/packages/server/package.json index 3242f0ba9..d6388aa66 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -131,7 +131,7 @@ "zod-validation-error": "catalog:" }, "devDependencies": { - "@sveltejs/kit": "^2.48.3", + "@sveltejs/kit": "catalog:", "@types/body-parser": "^1.19.6", "@types/express": "^5.0.0", "@types/supertest": "^6.0.3", @@ -144,8 +144,8 @@ "express": "^5.0.0", "fastify": "^5.6.1", "fastify-plugin": "^5.1.0", - "h3": "^1.15.4", - "hono": "^4.6.3", + "h3": "^1.15.5", + "hono": "^4.11.10", "next": "catalog:", "nuxt": "catalog:", "supertest": "^7.1.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a950c4bb5..95980553f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -7,8 +7,8 @@ settings: catalogs: default: '@sveltejs/kit': - specifier: 2.49.1 - version: 2.49.1 + specifier: 2.53.2 + version: 2.53.2 '@tanstack/query-core': specifier: 5.90.2 version: 5.90.2 @@ -55,11 +55,11 @@ catalogs: specifier: ^3.16.1 version: 3.16.1 next: - specifier: 16.0.10 - version: 16.0.10 + specifier: 16.1.6 + version: 16.1.6 nuxt: - specifier: 4.2.2 - version: 4.2.2 + specifier: 4.3.1 + version: 4.3.1 pg: specifier: ^8.13.1 version: 8.16.3 @@ -76,11 +76,11 @@ catalogs: specifier: ^1.13.0 version: 1.13.0 svelte: - specifier: 5.45.6 - version: 5.45.6 + specifier: 5.53.5 + version: 5.53.5 tmp: - specifier: ^0.2.3 - version: 0.2.3 + specifier: ^0.2.4 + version: 0.2.5 ts-pattern: specifier: ^5.7.1 version: 5.7.1 @@ -97,6 +97,11 @@ catalogs: specifier: ^4.0.1 version: 4.0.1 +overrides: + cookie@<0.7.0: '>=0.7.0' + lodash-es@>=4.0.0 <=4.17.22: '>=4.17.23' + lodash@>=4.0.0 <=4.17.22: '>=4.17.23' + importers: .: @@ -163,11 +168,11 @@ importers: version: 5.7.1 devDependencies: '@better-auth/cli': - specifier: 1.4.17 - version: 1.4.17(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.1.2)(kysely@0.28.8)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.0.1)(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.46.1)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + specifier: 1.4.19 + version: 1.4.19(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.1.2)(kysely@0.28.8)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.0.1)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) '@better-auth/core': - specifier: 1.4.17 - version: 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) + specifier: 1.4.19 + version: 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) '@types/tmp': specifier: 'catalog:' version: 0.2.6 @@ -184,11 +189,11 @@ importers: specifier: workspace:* version: link:../../config/vitest-config better-auth: - specifier: 1.4.17 - version: 1.4.17(16db68be254775c88fdefd75497fac00) + specifier: 1.4.19 + version: 1.4.19(824ab937a8901dc918eb211f1d922b51) tmp: specifier: 'catalog:' - version: 0.2.3 + version: 0.2.5 packages/cli: dependencies: @@ -297,7 +302,7 @@ importers: version: link:../config/vitest-config tmp: specifier: 'catalog:' - version: 0.2.3 + version: 0.2.5 packages/clients/client-helpers: dependencies: @@ -350,7 +355,7 @@ importers: devDependencies: '@sveltejs/package': specifier: ^2.5.7 - version: 2.5.7(svelte@5.45.6)(typescript@5.9.3) + version: 2.5.7(svelte@5.53.5)(typescript@5.9.3) '@tanstack/query-core': specifier: 'catalog:' version: 5.90.2 @@ -359,7 +364,7 @@ importers: version: 5.90.2(react@19.2.0) '@tanstack/svelte-query': specifier: 'catalog:' - version: 6.0.10(svelte@5.45.6) + version: 6.0.10(svelte@5.53.5) '@tanstack/vue-query': specifier: 'catalog:' version: 5.90.2(vue@3.5.22(typescript@5.9.3)) @@ -404,7 +409,7 @@ importers: version: 19.2.0 svelte: specifier: 'catalog:' - version: 5.45.6 + version: 5.53.5 vue: specifier: 'catalog:' version: 3.5.22(typescript@5.9.3) @@ -512,7 +517,7 @@ importers: version: 3.5.0 tmp: specifier: 'catalog:' - version: 0.2.3 + version: 0.2.5 packages/orm: dependencies: @@ -708,8 +713,8 @@ importers: version: 4.0.1(zod@4.1.12) devDependencies: '@sveltejs/kit': - specifier: ^2.48.3 - version: 2.48.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + specifier: 'catalog:' + version: 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@types/body-parser': specifier: ^1.19.6 version: 1.19.6 @@ -733,7 +738,7 @@ importers: version: link:../config/vitest-config body-parser: specifier: ^2.2.0 - version: 2.2.0 + version: 2.2.2 elysia: specifier: ^1.4.18 version: 1.4.18(@sinclair/typebox@0.34.41)(exact-mirror@0.2.2(@sinclair/typebox@0.34.41))(file-type@21.0.0)(openapi-types@12.1.3)(typescript@5.9.3) @@ -742,22 +747,22 @@ importers: version: 5.1.0 fastify: specifier: ^5.6.1 - version: 5.6.1 + version: 5.7.4 fastify-plugin: specifier: ^5.1.0 version: 5.1.0 h3: - specifier: ^1.15.4 - version: 1.15.4 + specifier: ^1.15.5 + version: 1.15.5 hono: - specifier: ^4.6.3 - version: 4.10.3 + specifier: ^4.11.10 + version: 4.12.2 next: specifier: 'catalog:' - version: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) nuxt: specifier: 'catalog:' - version: 4.2.2(6ed21839bfb37f6518a04a21db8dd1a5) + version: 4.3.1(7e2836fb0faa0d6f7376f741b393f215) supertest: specifier: ^7.1.4 version: 7.1.4 @@ -805,7 +810,7 @@ importers: version: 6.19.0(magicast@0.3.5)(typescript@5.9.3) tmp: specifier: 'catalog:' - version: 0.2.3 + version: 0.2.5 ts-pattern: specifier: 'catalog:' version: 5.7.1 @@ -882,7 +887,7 @@ importers: version: 2.0.8 next: specifier: 'catalog:' - version: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + version: 16.1.6(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) react: specifier: 'catalog:' version: 19.2.0 @@ -925,7 +930,7 @@ importers: dependencies: '@tailwindcss/vite': specifier: ^4.1.18 - version: 4.1.18(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + version: 4.1.18(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@tanstack/vue-query': specifier: 'catalog:' version: 5.90.2(vue@3.5.22(typescript@5.9.3)) @@ -949,7 +954,7 @@ importers: version: 2.0.8 nuxt: specifier: 'catalog:' - version: 4.2.2(8d53318c89c53efd506d8dd328c0edc0) + version: 4.3.1(7e2836fb0faa0d6f7376f741b393f215) tailwindcss: specifier: ^4.1.18 version: 4.1.18 @@ -1008,7 +1013,7 @@ importers: dependencies: '@tanstack/svelte-query': specifier: 'catalog:' - version: 6.0.10(svelte@5.45.6) + version: 6.0.10(svelte@5.53.5) '@zenstackhq/orm': specifier: workspace:* version: link:../../packages/orm @@ -1033,13 +1038,13 @@ importers: devDependencies: '@sveltejs/adapter-auto': specifier: ^7.0.0 - version: 7.0.0(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))) + version: 7.0.0(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))) '@sveltejs/kit': specifier: 'catalog:' - version: 2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + version: 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@sveltejs/vite-plugin-svelte': specifier: ^6.2.1 - version: 6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + version: 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@tailwindcss/vite': specifier: ^4.1.17 version: 4.1.18(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) @@ -1057,10 +1062,10 @@ importers: version: 0.7.2(prettier@3.8.1) svelte: specifier: 'catalog:' - version: 5.45.6 + version: 5.53.5 svelte-check: specifier: ^4.3.4 - version: 4.3.5(picomatch@4.0.3)(svelte@5.45.6)(typescript@5.9.3) + version: 4.3.5(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3) tailwindcss: specifier: ^4.1.17 version: 4.1.18 @@ -1272,14 +1277,26 @@ packages: resolution: {integrity: sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==} engines: {node: '>=6.9.0'} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.28.5': resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + '@babel/core@7.28.5': resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} engines: {node: '>=6.9.0'} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + '@babel/generator@7.28.5': resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} engines: {node: '>=6.9.0'} @@ -1288,6 +1305,10 @@ packages: resolution: {integrity: sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==} engines: {node: '>=6.9.0'} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + '@babel/helper-annotate-as-pure@7.27.3': resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} engines: {node: '>=6.9.0'} @@ -1296,8 +1317,12 @@ packages: resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} engines: {node: '>=6.9.0'} - '@babel/helper-create-class-features-plugin@7.28.5': - resolution: {integrity: sha512-q3WC4JfdODypvxArsJQROfupPBq9+lMwjKq7C33GhbFYJsufD0yd/ziwD+hJucLeWsnFPWZjsU2DNFqBPE7jwQ==} + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1334,16 +1359,12 @@ packages: resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} - engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.28.6': resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} - '@babel/helper-replace-supers@7.27.1': - resolution: {integrity: sha512-7EHz6qDZc8RYS5ElPoShMheWvEgERonFCs7IAonWLLUTXW59DP14bCZt89/GKyreYn8g3S83m21FelHKbeDCKA==} + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 @@ -1368,6 +1389,10 @@ packages: resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + '@babel/parser@7.28.5': resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} engines: {node: '>=6.0.0'} @@ -1383,20 +1408,14 @@ packages: engines: {node: '>=6.0.0'} hasBin: true - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.28.6': resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1431,8 +1450,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-transform-typescript@7.28.5': - resolution: {integrity: sha512-x2Qa+v/CuEoX7Dr31iAfr0IhInrVOWZU/2vJMJ00FOR/2nM0BcBEclpaf9sWCDc+v5e9dMrhSH8/atq/kX7+bA==} + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -1469,6 +1488,10 @@ packages: resolution: {integrity: sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + '@babel/types@7.28.5': resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} engines: {node: '>=6.9.0'} @@ -1485,12 +1508,12 @@ packages: resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==} engines: {node: '>=18'} - '@better-auth/cli@1.4.17': - resolution: {integrity: sha512-GT0epRZCRAxwDnDNVsAVXx2W4PNjBfm8NwrtjxfYKRHsnrCHkT0N/cZyKCYG1sReN/wlsyYk8bJ6f1N1P4VT8w==} + '@better-auth/cli@1.4.19': + resolution: {integrity: sha512-kH1e+F8sPwcfEuhyNzFt3rdLo5SBTfw7a9m/hyMv1E7tk/yfsqOZRL0I5GR+Fkm0FJ7SoVA4LJREFV3S0Px8iA==} hasBin: true - '@better-auth/core@1.4.17': - resolution: {integrity: sha512-WSaEQDdUO6B1CzAmissN6j0lx9fM9lcslEYzlApB5UzFaBeAOHNUONTdglSyUs6/idiZBoRvt0t/qMXCgIU8ug==} + '@better-auth/core@1.4.19': + resolution: {integrity: sha512-uADLHG1jc5BnEJi7f6ijUN5DmPPRSj++7m/G19z3UqA3MVCo4Y4t1MMa4IIxLCqGDFv22drdfxescgW+HnIowA==} peerDependencies: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 @@ -1499,10 +1522,10 @@ packages: kysely: ^0.28.5 nanostores: ^1.0.1 - '@better-auth/telemetry@1.4.17': - resolution: {integrity: sha512-R1BC4e/bNjQbXu7lG6ubpgmsPj7IMqky5DvMlzAtnAJWJhh99pMh/n6w5gOHa0cqDZgEAuj75IPTxv+q3YiInA==} + '@better-auth/telemetry@1.4.19': + resolution: {integrity: sha512-ApGNS7olCTtDpKF8Ow3Z+jvFAirOj7c4RyFUpu8axklh3mH57ndpfUAUjhgA8UVoaaH/mnm/Tl884BlqiewLyw==} peerDependencies: - '@better-auth/core': 1.4.17 + '@better-auth/core': 1.4.19 '@better-auth/utils@0.3.0': resolution: {integrity: sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==} @@ -1510,8 +1533,8 @@ packages: '@better-fetch/fetch@1.1.21': resolution: {integrity: sha512-/ImESw0sskqlVR94jB+5+Pxjf+xBwDZF/N5+y2/q4EqD7IARUTSpPfIo8uf39SYpCxyOCtbyYpUrZ3F/k0zT4A==} - '@bomb.sh/tab@0.0.10': - resolution: {integrity: sha512-6ALS2rh/4LKn0Yxwm35V6LcgQuSiECHbqQo7+9g4rkgGyXZ0siOc8K+IuWIq/4u0Zkv2mevP9QSqgKhGIvLJMw==} + '@bomb.sh/tab@0.0.12': + resolution: {integrity: sha512-dYRwg4MqfHR5/BcTy285XOGRhjQFmNpaJBZ0tl2oU+RY595MQ5ApTF6j3OvauPAooHL6cfoOZMySQrOQztT8RQ==} hasBin: true peerDependencies: cac: ^6.7.14 @@ -1558,17 +1581,17 @@ packages: '@clack/core@0.5.0': resolution: {integrity: sha512-p3y0FIOwaYRUPRcMO7+dlmLh8PSRcrjuTndsiA0WAFbWES0mLZlrjVoBRZ9DzkPFJZG6KGkJmoEAY0ZcVWTkow==} - '@clack/core@1.0.0-alpha.7': - resolution: {integrity: sha512-3vdh6Ar09D14rVxJZIm3VQJkU+ZOKKT5I5cC0cOVazy70CNyYYjiwRj9unwalhESndgxx6bGc/m6Hhs4EKF5XQ==} + '@clack/core@1.0.1': + resolution: {integrity: sha512-WKeyK3NOBwDOzagPR5H08rFk9D/WuN705yEbuZvKqlkmoLM2woKtXb10OO2k1NoSU4SFG947i2/SCYh+2u5e4g==} '@clack/prompts@0.11.0': resolution: {integrity: sha512-pMN5FcrEw9hUkZA4f+zLlzivQSeQf5dRGJjSUbvVYDLvpKCdQx5OaknvKzgbtXOizhP+SJJJjqEbOe55uKKfAw==} - '@clack/prompts@1.0.0-alpha.8': - resolution: {integrity: sha512-YZGC4BmTKSF5OturNKEz/y4xNjYGmGk6NI785CQucJ7OEdX0qbMmL/zok+9bL6c7qE3WSYffyK5grh2RnkGNtQ==} + '@clack/prompts@1.0.1': + resolution: {integrity: sha512-/42G73JkuYdyWZ6m8d/CJtBrGl1Hegyc7Fy78m5Ob+jF85TOUmLR5XLce/U3LxYAw0kJ8CT5aI99RIvPHcGp/Q==} - '@cloudflare/kv-asset-handler@0.4.0': - resolution: {integrity: sha512-+tv3z+SPp+gqTIcImN9o0hqE9xyfQjI1XD9pL6NuKjua9B1y7mNYv0S9cP+QEbA4ppVgGZEmKOvHX5G5Ei1CVA==} + '@cloudflare/kv-asset-handler@0.4.2': + resolution: {integrity: sha512-SIOD2DxrRRwQ+jgzlXCqoEFiKOFqaPjhnNTGKXSRLvp1HiOvapLaFG2kEr9dYQTYe8rKrd9uvDUzmAITeNyaHQ==} engines: {node: '>=18.0.0'} '@csstools/color-helpers@6.0.1': @@ -1602,8 +1625,8 @@ packages: resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} engines: {node: '>=20.19.0'} - '@dxup/nuxt@0.2.2': - resolution: {integrity: sha512-RNpJjDZs9+JcT9N87AnOuHsNM75DEd58itADNd/s1LIF6BZbTLZV0xxilJZb55lntn4TYvscTaXLCBX2fq9CXg==} + '@dxup/nuxt@0.3.2': + resolution: {integrity: sha512-2f2usP4oLNsIGjPprvABe3f3GWuIhIDp0169pGLFxTDRI5A4d4sBbGpR+tD9bGZCT+1Btb6Q2GKlyv3LkDCW5g==} '@dxup/unimport@0.1.2': resolution: {integrity: sha512-/B8YJGPzaYq1NbsQmwgP8EZqg40NpTw4ZB3suuI0TplbxKHeK94jeaawLmVhCv+YwUnOpiWEz9U6SeThku/8JQ==} @@ -1628,12 +1651,6 @@ packages: '@emnapi/wasi-threads@1.1.0': resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} - '@esbuild/aix-ppc64@0.25.11': - resolution: {integrity: sha512-Xt1dOL13m8u0WE8iplx9Ibbm+hFAO0GsU2P34UNoDGvZYkY8ifSiy6Zuc1lYxfG7svWE2fzqCUmFp5HCn51gJg==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.25.5': resolution: {integrity: sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==} engines: {node: '>=18'} @@ -1646,11 +1663,11 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/android-arm64@0.25.11': - resolution: {integrity: sha512-9slpyFBc4FPPz48+f6jyiXOx/Y4v34TUeDDXJpZqAWQn/08lKGeD8aDp9TMn9jDz2CiEuHwfhRmGBvpnd/PWIQ==} + '@esbuild/aix-ppc64@0.27.3': + resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==} engines: {node: '>=18'} - cpu: [arm64] - os: [android] + cpu: [ppc64] + os: [aix] '@esbuild/android-arm64@0.25.5': resolution: {integrity: sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==} @@ -1664,10 +1681,10 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm@0.25.11': - resolution: {integrity: sha512-uoa7dU+Dt3HYsethkJ1k6Z9YdcHjTrSb5NUy66ZfZaSV8hEYGD5ZHbEMXnqLFlbBflLsl89Zke7CAdDJ4JI+Gg==} + '@esbuild/android-arm64@0.27.3': + resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==} engines: {node: '>=18'} - cpu: [arm] + cpu: [arm64] os: [android] '@esbuild/android-arm@0.25.5': @@ -1682,10 +1699,10 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-x64@0.25.11': - resolution: {integrity: sha512-Sgiab4xBjPU1QoPEIqS3Xx+R2lezu0LKIEcYe6pftr56PqPygbB7+szVnzoShbx64MUupqoE0KyRlN7gezbl8g==} + '@esbuild/android-arm@0.27.3': + resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm] os: [android] '@esbuild/android-x64@0.25.5': @@ -1700,11 +1717,11 @@ packages: cpu: [x64] os: [android] - '@esbuild/darwin-arm64@0.25.11': - resolution: {integrity: sha512-VekY0PBCukppoQrycFxUqkCojnTQhdec0vevUL/EDOCnXd9LKWqD/bHwMPzigIJXPhC59Vd1WFIL57SKs2mg4w==} + '@esbuild/android-x64@0.27.3': + resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] + cpu: [x64] + os: [android] '@esbuild/darwin-arm64@0.25.5': resolution: {integrity: sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==} @@ -1718,10 +1735,10 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-x64@0.25.11': - resolution: {integrity: sha512-+hfp3yfBalNEpTGp9loYgbknjR695HkqtY3d3/JjSRUyPg/xd6q+mQqIb5qdywnDxRZykIHs3axEqU6l1+oWEQ==} + '@esbuild/darwin-arm64@0.27.3': + resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [darwin] '@esbuild/darwin-x64@0.25.5': @@ -1736,11 +1753,11 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/freebsd-arm64@0.25.11': - resolution: {integrity: sha512-CmKjrnayyTJF2eVuO//uSjl/K3KsMIeYeyN7FyDBjsR3lnSJHaXlVoAK8DZa7lXWChbuOk7NjAc7ygAwrnPBhA==} + '@esbuild/darwin-x64@0.27.3': + resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==} engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] + cpu: [x64] + os: [darwin] '@esbuild/freebsd-arm64@0.25.5': resolution: {integrity: sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==} @@ -1754,10 +1771,10 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-x64@0.25.11': - resolution: {integrity: sha512-Dyq+5oscTJvMaYPvW3x3FLpi2+gSZTCE/1ffdwuM6G1ARang/mb3jvjxs0mw6n3Lsw84ocfo9CrNMqc5lTfGOw==} + '@esbuild/freebsd-arm64@0.27.3': + resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [freebsd] '@esbuild/freebsd-x64@0.25.5': @@ -1772,11 +1789,11 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/linux-arm64@0.25.11': - resolution: {integrity: sha512-Qr8AzcplUhGvdyUF08A1kHU3Vr2O88xxP0Tm8GcdVOUm25XYcMPp2YqSVHbLuXzYQMf9Bh/iKx7YPqECs6ffLA==} + '@esbuild/freebsd-x64@0.27.3': + resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==} engines: {node: '>=18'} - cpu: [arm64] - os: [linux] + cpu: [x64] + os: [freebsd] '@esbuild/linux-arm64@0.25.5': resolution: {integrity: sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==} @@ -1790,10 +1807,10 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm@0.25.11': - resolution: {integrity: sha512-TBMv6B4kCfrGJ8cUPo7vd6NECZH/8hPpBHHlYI3qzoYFvWu2AdTvZNuU/7hsbKWqu/COU7NIK12dHAAqBLLXgw==} + '@esbuild/linux-arm64@0.27.3': + resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==} engines: {node: '>=18'} - cpu: [arm] + cpu: [arm64] os: [linux] '@esbuild/linux-arm@0.25.5': @@ -1808,10 +1825,10 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-ia32@0.25.11': - resolution: {integrity: sha512-TmnJg8BMGPehs5JKrCLqyWTVAvielc615jbkOirATQvWWB1NMXY77oLMzsUjRLa0+ngecEmDGqt5jiDC6bfvOw==} + '@esbuild/linux-arm@0.27.3': + resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [arm] os: [linux] '@esbuild/linux-ia32@0.25.5': @@ -1826,10 +1843,10 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-loong64@0.25.11': - resolution: {integrity: sha512-DIGXL2+gvDaXlaq8xruNXUJdT5tF+SBbJQKbWy/0J7OhU8gOHOzKmGIlfTTl6nHaCOoipxQbuJi7O++ldrxgMw==} + '@esbuild/linux-ia32@0.27.3': + resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==} engines: {node: '>=18'} - cpu: [loong64] + cpu: [ia32] os: [linux] '@esbuild/linux-loong64@0.25.5': @@ -1844,10 +1861,10 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-mips64el@0.25.11': - resolution: {integrity: sha512-Osx1nALUJu4pU43o9OyjSCXokFkFbyzjXb6VhGIJZQ5JZi8ylCQ9/LFagolPsHtgw6himDSyb5ETSfmp4rpiKQ==} + '@esbuild/linux-loong64@0.27.3': + resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==} engines: {node: '>=18'} - cpu: [mips64el] + cpu: [loong64] os: [linux] '@esbuild/linux-mips64el@0.25.5': @@ -1862,10 +1879,10 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-ppc64@0.25.11': - resolution: {integrity: sha512-nbLFgsQQEsBa8XSgSTSlrnBSrpoWh7ioFDUmwo158gIm5NNP+17IYmNWzaIzWmgCxq56vfr34xGkOcZ7jX6CPw==} + '@esbuild/linux-mips64el@0.27.3': + resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==} engines: {node: '>=18'} - cpu: [ppc64] + cpu: [mips64el] os: [linux] '@esbuild/linux-ppc64@0.25.5': @@ -1880,10 +1897,10 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-riscv64@0.25.11': - resolution: {integrity: sha512-HfyAmqZi9uBAbgKYP1yGuI7tSREXwIb438q0nqvlpxAOs3XnZ8RsisRfmVsgV486NdjD7Mw2UrFSw51lzUk1ww==} + '@esbuild/linux-ppc64@0.27.3': + resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==} engines: {node: '>=18'} - cpu: [riscv64] + cpu: [ppc64] os: [linux] '@esbuild/linux-riscv64@0.25.5': @@ -1898,10 +1915,10 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-s390x@0.25.11': - resolution: {integrity: sha512-HjLqVgSSYnVXRisyfmzsH6mXqyvj0SA7pG5g+9W7ESgwA70AXYNpfKBqh1KbTxmQVaYxpzA/SvlB9oclGPbApw==} + '@esbuild/linux-riscv64@0.27.3': + resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==} engines: {node: '>=18'} - cpu: [s390x] + cpu: [riscv64] os: [linux] '@esbuild/linux-s390x@0.25.5': @@ -1916,10 +1933,10 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-x64@0.25.11': - resolution: {integrity: sha512-HSFAT4+WYjIhrHxKBwGmOOSpphjYkcswF449j6EjsjbinTZbp8PJtjsVK1XFJStdzXdy/jaddAep2FGY+wyFAQ==} + '@esbuild/linux-s390x@0.27.3': + resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==} engines: {node: '>=18'} - cpu: [x64] + cpu: [s390x] os: [linux] '@esbuild/linux-x64@0.25.5': @@ -1934,11 +1951,11 @@ packages: cpu: [x64] os: [linux] - '@esbuild/netbsd-arm64@0.25.11': - resolution: {integrity: sha512-hr9Oxj1Fa4r04dNpWr3P8QKVVsjQhqrMSUzZzf+LZcYjZNqhA3IAfPQdEh1FLVUJSiu6sgAwp3OmwBfbFgG2Xg==} + '@esbuild/linux-x64@0.27.3': + resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==} engines: {node: '>=18'} - cpu: [arm64] - os: [netbsd] + cpu: [x64] + os: [linux] '@esbuild/netbsd-arm64@0.25.5': resolution: {integrity: sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==} @@ -1952,10 +1969,10 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-x64@0.25.11': - resolution: {integrity: sha512-u7tKA+qbzBydyj0vgpu+5h5AeudxOAGncb8N6C9Kh1N4n7wU1Xw1JDApsRjpShRpXRQlJLb9wY28ELpwdPcZ7A==} + '@esbuild/netbsd-arm64@0.27.3': + resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [netbsd] '@esbuild/netbsd-x64@0.25.5': @@ -1970,11 +1987,11 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/openbsd-arm64@0.25.11': - resolution: {integrity: sha512-Qq6YHhayieor3DxFOoYM1q0q1uMFYb7cSpLD2qzDSvK1NAvqFi8Xgivv0cFC6J+hWVw2teCYltyy9/m/14ryHg==} + '@esbuild/netbsd-x64@0.27.3': + resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==} engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] + cpu: [x64] + os: [netbsd] '@esbuild/openbsd-arm64@0.25.5': resolution: {integrity: sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==} @@ -1988,10 +2005,10 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-x64@0.25.11': - resolution: {integrity: sha512-CN+7c++kkbrckTOz5hrehxWN7uIhFFlmS/hqziSFVWpAzpWrQoAG4chH+nN3Be+Kzv/uuo7zhX716x3Sn2Jduw==} + '@esbuild/openbsd-arm64@0.27.3': + resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==} engines: {node: '>=18'} - cpu: [x64] + cpu: [arm64] os: [openbsd] '@esbuild/openbsd-x64@0.25.5': @@ -2006,11 +2023,11 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openharmony-arm64@0.25.11': - resolution: {integrity: sha512-rOREuNIQgaiR+9QuNkbkxubbp8MSO9rONmwP5nKncnWJ9v5jQ4JxFnLu4zDSRPf3x4u+2VN4pM4RdyIzDty/wQ==} + '@esbuild/openbsd-x64@0.27.3': + resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==} engines: {node: '>=18'} - cpu: [arm64] - os: [openharmony] + cpu: [x64] + os: [openbsd] '@esbuild/openharmony-arm64@0.27.2': resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} @@ -2018,11 +2035,11 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/sunos-x64@0.25.11': - resolution: {integrity: sha512-nq2xdYaWxyg9DcIyXkZhcYulC6pQ2FuCgem3LI92IwMgIZ69KHeY8T4Y88pcwoLIjbed8n36CyKoYRDygNSGhA==} + '@esbuild/openharmony-arm64@0.27.3': + resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==} engines: {node: '>=18'} - cpu: [x64] - os: [sunos] + cpu: [arm64] + os: [openharmony] '@esbuild/sunos-x64@0.25.5': resolution: {integrity: sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==} @@ -2036,11 +2053,11 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/win32-arm64@0.25.11': - resolution: {integrity: sha512-3XxECOWJq1qMZ3MN8srCJ/QfoLpL+VaxD/WfNRm1O3B4+AZ/BnLVgFbUV3eiRYDMXetciH16dwPbbHqwe1uU0Q==} + '@esbuild/sunos-x64@0.27.3': + resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==} engines: {node: '>=18'} - cpu: [arm64] - os: [win32] + cpu: [x64] + os: [sunos] '@esbuild/win32-arm64@0.25.5': resolution: {integrity: sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==} @@ -2054,10 +2071,10 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-ia32@0.25.11': - resolution: {integrity: sha512-3ukss6gb9XZ8TlRyJlgLn17ecsK4NSQTmdIXRASVsiS2sQ6zPPZklNJT5GR5tE/MUarymmy8kCEf5xPCNCqVOA==} + '@esbuild/win32-arm64@0.27.3': + resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==} engines: {node: '>=18'} - cpu: [ia32] + cpu: [arm64] os: [win32] '@esbuild/win32-ia32@0.25.5': @@ -2072,10 +2089,10 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-x64@0.25.11': - resolution: {integrity: sha512-D7Hpz6A2L4hzsRpPaCYkQnGOotdUpDzSGRIv9I+1ITdHROSFUWW95ZPZWQmGka1Fg7W3zFJowyn9WGwMJ0+KPA==} + '@esbuild/win32-ia32@0.27.3': + resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==} engines: {node: '>=18'} - cpu: [x64] + cpu: [ia32] os: [win32] '@esbuild/win32-x64@0.25.5': @@ -2090,6 +2107,12 @@ packages: cpu: [x64] os: [win32] + '@esbuild/win32-x64@0.27.3': + resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + '@eslint-community/eslint-utils@4.7.0': resolution: {integrity: sha512-dyybb3AcajC7uha6CvhdVRJqaKyn7w2YKqKyAN37NKYgZT36w+iRb0Dymmc5qEJ549c/S31cMMSFd75bteCpCw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2112,8 +2135,8 @@ packages: resolution: {integrity: sha512-qIbV0/JZr7iSDjqAc60IqbLdsj9GDt16xQtWD+B78d/HAlvysGdZZ6rpJHGAc2T0FQx1X6thsSPdnoiGKdNtdg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/core@0.15.0': - resolution: {integrity: sha512-b7ePw78tEWWkpgZCDYkbqDOP8dmM6qe+AOC6iuJqlq1R/0ahMAeH3qynpnqKFGkMltrp44ohV4ubGyvLX28tzw==} + '@eslint/core@0.15.2': + resolution: {integrity: sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@3.3.1': @@ -2128,8 +2151,8 @@ packages: resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/plugin-kit@0.3.2': - resolution: {integrity: sha512-4SaFZCNfJqvk/kenHpI8xvN42DMaoycy4PzKc5otHxRswww1kAt82OlBuwRVLofCACCTZEcla2Ydxv8scMXaTg==} + '@eslint/plugin-kit@0.3.5': + resolution: {integrity: sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@fastify/ajv-compiler@4.0.5': @@ -2296,16 +2319,8 @@ packages: cpu: [x64] os: [win32] - '@ioredis/commands@1.4.0': - resolution: {integrity: sha512-aFT2yemJJo+TZCmieA7qnYGQooOS7QfNmYrzGtsYd3g9j5iDP8AimYYAesf79ohjbLG12XxC4nG5DyEnC88AsQ==} - - '@isaacs/balanced-match@4.0.1': - resolution: {integrity: sha512-yzMTt9lEb8Gv7zRioUilSglI0c0smZ9k5D65677DLWLtWJaXIS3CqcGyUFByYKlnUj6TkjLVs54fBl6+TiGQDQ==} - engines: {node: 20 || >=22} - - '@isaacs/brace-expansion@5.0.0': - resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==} - engines: {node: 20 || >=22} + '@ioredis/commands@1.5.0': + resolution: {integrity: sha512-eUgLqrMf8nJkZxT24JvVRrQya1vZkQh8BBeYNwGDqa5I0VUi8ACx7uFvAaLxintokpTenkK6DASvo/bvNbBGow==} '@isaacs/cliui@8.0.2': resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} @@ -2370,59 +2385,59 @@ packages: '@napi-rs/wasm-runtime@0.2.12': resolution: {integrity: sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==} - '@napi-rs/wasm-runtime@1.1.0': - resolution: {integrity: sha512-Fq6DJW+Bb5jaWE69/qOE0D1TUN9+6uWhCeZpdnSBk14pjLcCWR7Q8n49PTSPHazM37JqrsdpEthXy2xn6jWWiA==} + '@napi-rs/wasm-runtime@1.1.1': + resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==} - '@next/env@16.0.10': - resolution: {integrity: sha512-8tuaQkyDVgeONQ1MeT9Mkk8pQmZapMKFh5B+OrFUlG3rVmYTXcXlBetBgTurKXGaIZvkoqRT9JL5K3phXcgang==} + '@next/env@16.1.6': + resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==} '@next/eslint-plugin-next@16.0.1': resolution: {integrity: sha512-g4Cqmv/gyFEXNeVB2HkqDlYKfy+YrlM2k8AVIO/YQVEPfhVruH1VA99uT1zELLnPLIeOnx8IZ6Ddso0asfTIdw==} - '@next/swc-darwin-arm64@16.0.10': - resolution: {integrity: sha512-4XgdKtdVsaflErz+B5XeG0T5PeXKDdruDf3CRpnhN+8UebNa5N2H58+3GDgpn/9GBurrQ1uWW768FfscwYkJRg==} + '@next/swc-darwin-arm64@16.1.6': + resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==} engines: {node: '>= 10'} cpu: [arm64] os: [darwin] - '@next/swc-darwin-x64@16.0.10': - resolution: {integrity: sha512-spbEObMvRKkQ3CkYVOME+ocPDFo5UqHb8EMTS78/0mQ+O1nqE8toHJVioZo4TvebATxgA8XMTHHrScPrn68OGw==} + '@next/swc-darwin-x64@16.1.6': + resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==} engines: {node: '>= 10'} cpu: [x64] os: [darwin] - '@next/swc-linux-arm64-gnu@16.0.10': - resolution: {integrity: sha512-uQtWE3X0iGB8apTIskOMi2w/MKONrPOUCi5yLO+v3O8Mb5c7K4Q5KD1jvTpTF5gJKa3VH/ijKjKUq9O9UhwOYw==} + '@next/swc-linux-arm64-gnu@16.1.6': + resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-arm64-musl@16.0.10': - resolution: {integrity: sha512-llA+hiDTrYvyWI21Z0L1GiXwjQaanPVQQwru5peOgtooeJ8qx3tlqRV2P7uH2pKQaUfHxI/WVarvI5oYgGxaTw==} + '@next/swc-linux-arm64-musl@16.1.6': + resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - '@next/swc-linux-x64-gnu@16.0.10': - resolution: {integrity: sha512-AK2q5H0+a9nsXbeZ3FZdMtbtu9jxW4R/NgzZ6+lrTm3d6Zb7jYrWcgjcpM1k8uuqlSy4xIyPR2YiuUr+wXsavA==} + '@next/swc-linux-x64-gnu@16.1.6': + resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-linux-x64-musl@16.0.10': - resolution: {integrity: sha512-1TDG9PDKivNw5550S111gsO4RGennLVl9cipPhtkXIFVwo31YZ73nEbLjNC8qG3SgTz/QZyYyaFYMeY4BKZR/g==} + '@next/swc-linux-x64-musl@16.1.6': + resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - '@next/swc-win32-arm64-msvc@16.0.10': - resolution: {integrity: sha512-aEZIS4Hh32xdJQbHz121pyuVZniSNoqDVx1yIr2hy+ZwJGipeqnMZBJHyMxv2tiuAXGx6/xpTcQJ6btIiBjgmg==} + '@next/swc-win32-arm64-msvc@16.1.6': + resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==} engines: {node: '>= 10'} cpu: [arm64] os: [win32] - '@next/swc-win32-x64-msvc@16.0.10': - resolution: {integrity: sha512-E+njfCoFLb01RAFEnGZn6ERoOqhK1Gl3Lfz1Kjnj0Ulfu7oJbuMyvBKNj/bw8XZnenHDASlygTjZICQW+rYW1Q==} + '@next/swc-win32-x64-msvc@16.1.6': + resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==} engines: {node: '>= 10'} cpu: [x64] os: [win32] @@ -2455,10 +2470,15 @@ packages: resolution: {integrity: sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==} engines: {node: '>=12.4.0'} - '@nuxt/cli@3.31.3': - resolution: {integrity: sha512-K0T1ZpBXnlb41NU/RWf1F0U0C14KzlEXCoaSgD2y8BiLoCBWcgQ1UAlRtx4cThqWbJmIxaNZZTDL0NZ9d1U7ag==} + '@nuxt/cli@3.33.1': + resolution: {integrity: sha512-/sCrcI0WemING9zASaXPgPDY7PrQTPlRyCXlSgGx8VwRAkWbxGaPhIc3kZQikgLwVAwy+muWVV4Wks8OTtW5Tw==} engines: {node: ^16.10.0 || >=18.0.0} hasBin: true + peerDependencies: + '@nuxt/schema': ^4.3.0 + peerDependenciesMeta: + '@nuxt/schema': + optional: true '@nuxt/devalue@2.0.2': resolution: {integrity: sha512-GBzP8zOc7CGWyFQS6dv1lQz8VVpz5C2yRszbXufwG/9zhStTIH50EtD87NmWbTMwXDvZLNg8GIpb1UFdH93JCA==} @@ -2482,34 +2502,32 @@ packages: '@vitejs/devtools': optional: true - '@nuxt/kit@3.20.0': - resolution: {integrity: sha512-EoF1Gf0SPj9vxgAIcGEH+a4PRLC7Dwsy21K6f5+POzylT8DgssN8zL5pwXC+X7OcfzBrwYFh7mM7phvh7ubgeg==} - engines: {node: '>=18.12.0'} - - '@nuxt/kit@4.2.2': - resolution: {integrity: sha512-ZAgYBrPz/yhVgDznBNdQj2vhmOp31haJbO0I0iah/P9atw+OHH7NJLUZ3PK+LOz/0fblKTN1XJVSi8YQ1TQ0KA==} + '@nuxt/kit@4.3.1': + resolution: {integrity: sha512-UjBFt72dnpc+83BV3OIbCT0YHLevJtgJCHpxMX0YRKWLDhhbcDdUse87GtsQBrjvOzK7WUNUYLDS/hQLYev5rA==} engines: {node: '>=18.12.0'} - '@nuxt/nitro-server@4.2.2': - resolution: {integrity: sha512-lDITf4n5bHQ6a5MO7pvkpdQbPdWAUgSvztSHCfui/3ioLZsM2XntlN02ue6GSoh3oV9H4xSB3qGa+qlSjgxN0A==} + '@nuxt/nitro-server@4.3.1': + resolution: {integrity: sha512-4aNiM69Re02gI1ywnDND0m6QdVKXhWzDdtvl/16veytdHZj3FSq57ZCwOClNJ7HQkEMqXgS+bi6S2HmJX+et+g==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - nuxt: ^4.2.2 + nuxt: ^4.3.1 - '@nuxt/schema@4.2.2': - resolution: {integrity: sha512-lW/1MNpO01r5eR/VoeanQio8Lg4QpDklMOHa4mBHhhPNlBO1qiRtVYzjcnNdun3hujGauRaO9khGjv93Z5TZZA==} + '@nuxt/schema@4.3.1': + resolution: {integrity: sha512-S+wHJdYDuyk9I43Ej27y5BeWMZgi7R/UVql3b3qtT35d0fbpXW7fUenzhLRCCDC6O10sjguc6fcMcR9sMKvV8g==} engines: {node: ^14.18.0 || >=16.10.0} - '@nuxt/telemetry@2.6.6': - resolution: {integrity: sha512-Zh4HJLjzvm3Cq9w6sfzIFyH9ozK5ePYVfCUzzUQNiZojFsI2k1QkSBrVI9BGc6ArKXj/O6rkI6w7qQ+ouL8Cag==} + '@nuxt/telemetry@2.7.0': + resolution: {integrity: sha512-mrKC3NjAlBOooLLVTYcIUie1meipoYq5vkoESoVTEWTB34T3a0QJzOfOPch+HYlUR+5Lqy1zLMv6epHFgYAKLA==} engines: {node: '>=18.12.0'} hasBin: true + peerDependencies: + '@nuxt/kit': '>=3.0.0' - '@nuxt/vite-builder@4.2.2': - resolution: {integrity: sha512-Bot8fpJNtHZrM4cS1iSR7bEAZ1mFLAtJvD/JOSQ6kT62F4hSFWfMubMXOwDkLK2tnn3bnAdSqGy1nLNDBCahpQ==} + '@nuxt/vite-builder@4.3.1': + resolution: {integrity: sha512-LndnxPJzDUDbWAB8q5gZZN1mSOLHEyMOoj4T3pTdPydGf31QZdMR0V1fQ1fdRgtgNtWB3WLP0d1ZfaAOITsUpw==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: - nuxt: 4.2.2 + nuxt: 4.3.1 rolldown: ^1.0.0-beta.38 vue: ^3.3.4 peerDependenciesMeta: @@ -2525,272 +2543,362 @@ packages: '@open-draft/until@2.1.0': resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==} - '@oxc-minify/binding-android-arm64@0.102.0': - resolution: {integrity: sha512-pknM+ttJTwRr7ezn1v5K+o2P4RRjLAzKI10bjVDPybwWQ544AZW6jxm7/YDgF2yUbWEV9o7cAQPkIUOmCiW8vg==} + '@oxc-minify/binding-android-arm-eabi@0.112.0': + resolution: {integrity: sha512-m7TGBR2hjsBJIN9UJ909KBoKsuogo6CuLsHKvUIBXdjI0JVHP8g4ZHeB+BJpGn5LJdeSGDfz9MWiuXrZDRzunw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-minify/binding-android-arm64@0.112.0': + resolution: {integrity: sha512-RvxOOkzvP5NeeoraBtgNJSBqO+XzlS7DooxST/drAXCfO52GsmxVB1N7QmifrsTYtH8GC2z3DTFjZQ1w/AJOWg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-minify/binding-darwin-arm64@0.102.0': - resolution: {integrity: sha512-BDLiH41ZctNND38+GCEL3ZxFn9j7qMZJLrr6SLWMt8xlG4Sl64xTkZ0zeUy4RdVEatKKZdrRIhFZ2e5wPDQT6Q==} + '@oxc-minify/binding-darwin-arm64@0.112.0': + resolution: {integrity: sha512-hDslO3uVHza3kB9zkcsi25JzN65Gj5ZYty0OvylS11Mhg9ydCYxAzfQ/tISHW/YmV1NRUJX8+GGqM1cKmrHaTA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-minify/binding-darwin-x64@0.102.0': - resolution: {integrity: sha512-AcB8ZZ711w4hTDhMfMHNjT2d+hekTQ2XmNSUBqJdXB+a2bJbE50UCRq/nxXl44zkjaQTit3lcQbFvhk2wwKcpw==} + '@oxc-minify/binding-darwin-x64@0.112.0': + resolution: {integrity: sha512-mWA2Y5bUyNoGM+gSGGHesgtQ3LDWgpRe4zDGkBDovxNIiDLBXqu/7QcuS+G918w8oG9VYm1q1iinILer/2pD1Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-minify/binding-freebsd-x64@0.102.0': - resolution: {integrity: sha512-UlLEN9mR5QaviYVMWZQsN9DgAH3qyV67XUXDEzSrbVMLsqHsVHhFU8ZIeO0fxWTQW/cgpvldvKp9/+RdrggqWw==} + '@oxc-minify/binding-freebsd-x64@0.112.0': + resolution: {integrity: sha512-T7fsegxcy82xS0jWPXkz/BMhrkb3D7YOCiV0R9pDksjaov+iIFoNEWAoBsaC5NtpdzkX+bmffwDpu336EIfEeg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-minify/binding-linux-arm-gnueabihf@0.102.0': - resolution: {integrity: sha512-CWyCwedZrUt47n56/RwHSwKXxVI3p98hB0ntLaBNeH5qjjBujs9uOh4bQ0aAlzUWunT77b3/Y+xcQnmV42HN4A==} + '@oxc-minify/binding-linux-arm-gnueabihf@0.112.0': + resolution: {integrity: sha512-yePavbIilAcpVYc8vRsDCn3xJxHMXDZIiamyH9fuLosAHNELcLib4/JR4fhDk4NmHVagQH3kRhsnm5Q9cm3pAw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-minify/binding-linux-arm-musleabihf@0.112.0': + resolution: {integrity: sha512-lmPWLXtW6FspERhy97iP0hwbmLtL66xI29QQ9GpHmTiE4k+zv/FaefuV/Qw+LuHnmFSYzUNrLcxh4ulOZTIP2g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-minify/binding-linux-arm64-gnu@0.102.0': - resolution: {integrity: sha512-W/DCw+Ys8rXj4j38ylJ2l6Kvp6SV+eO5SUWA11imz7yCWntNL001KJyGQ9PJNUFHg0jbxe3yqm4M50v6miWzeA==} + '@oxc-minify/binding-linux-arm64-gnu@0.112.0': + resolution: {integrity: sha512-gySS5XqU5MKs/oCjsTlVm8zb8lqcNKHEANsaRmhW2qvGKJoeGwFb6Fbq6TLCZMRuk143mLbncbverBCa1c3dog==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxc-minify/binding-linux-arm64-musl@0.102.0': - resolution: {integrity: sha512-DyH/t/zSZHuX4Nn239oBteeMC4OP7B13EyXWX18Qg8aJoZ+lZo90WPGOvhP04zII33jJ7di+vrtAUhsX64lp+A==} + '@oxc-minify/binding-linux-arm64-musl@0.112.0': + resolution: {integrity: sha512-IRFMZX589lr3rjG0jc8N261/7wqFq2Vl0OMrJWeFls5BF8HiB+fRYuf0Zy2CyRH6NCY2vbdDdp+QCAavQGVsGw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxc-minify/binding-linux-riscv64-gnu@0.102.0': - resolution: {integrity: sha512-CMvzrmOg+Gs44E7TRK/IgrHYp+wwVJxVV8niUrDR2b3SsrCO3NQz5LI+7bM1qDbWnuu5Cl1aiitoMfjRY61dSg==} + '@oxc-minify/binding-linux-ppc64-gnu@0.112.0': + resolution: {integrity: sha512-V/69XqIW9hCUceDpcZh79oDg+F4ptEgIfKRENzYs41LRbSoJ7sNjjcW4zifqyviTvzcnXLgK4uoTyoymmNZBMQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-minify/binding-linux-riscv64-gnu@0.112.0': + resolution: {integrity: sha512-zghvexySyGXGNW+MutjZN7UGTyOQl56RWMlPe1gb+knBm/+0hf9qjk7Q6ofm2tSte+vQolPfQttifGl0dP9uvQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-minify/binding-linux-riscv64-musl@0.112.0': + resolution: {integrity: sha512-E4a8VUFDJPb2mPcc7J4NQQPi1ssHKF7/g4r6KD2+SBVERIaEEd3cGNqR7SG3g82/BLGV2UDoQe/WvZCkt5M/bQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxc-minify/binding-linux-s390x-gnu@0.102.0': - resolution: {integrity: sha512-tZWr6j2s0ddm9MTfWTI3myaAArg9GDy4UgvpF00kMQAjLcGUNhEEQbB9Bd9KtCvDQzaan8HQs0GVWUp+DWrymw==} + '@oxc-minify/binding-linux-s390x-gnu@0.112.0': + resolution: {integrity: sha512-2Hx87sK3y6jBV364Mvv0zyxiITIuy26Ixenv6pK7e+4an3HgNdhAj8nk3aLoLTTSvLik5/MaGhcZGEu9tYV1aA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@oxc-minify/binding-linux-x64-gnu@0.102.0': - resolution: {integrity: sha512-0YEKmAIun1bS+Iy5Shx6WOTSj3GuilVuctJjc5/vP8/EMTZ/RI8j0eq0Mu3UFPoT/bMULL3MBXuHuEIXmq7Ddg==} + '@oxc-minify/binding-linux-x64-gnu@0.112.0': + resolution: {integrity: sha512-2MSCnEPLk9ddSouMhJo78Xy2/JbYC80OYzWdR4yWTGSULsgH3d1VXg73DSwFL8vU7Ad9oK10DioBY2ww7sQTEg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxc-minify/binding-linux-x64-musl@0.102.0': - resolution: {integrity: sha512-Ew4QDpEsXoV+pG5+bJpheEy3GH436GBe6ASPB0X27Hh9cQ2gb1NVZ7cY7xJj68+fizwS/PtT8GHoG3uxyH17Pg==} + '@oxc-minify/binding-linux-x64-musl@0.112.0': + resolution: {integrity: sha512-HAPfmQKlkVi97/zRonVE9t/kKUG3ni+mOuU1Euw+3s37KwUuOJjmcwXdclVgXKBlTkCGO0FajPwW5dAJeIXCCw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxc-minify/binding-openharmony-arm64@0.102.0': - resolution: {integrity: sha512-wYPXS8IOu/sXiP3CGHJNPzZo4hfPAwJKevcFH2syvU2zyqUxym7hx6smfcK/mgJBiX7VchwArdGRwrEQKcBSaQ==} + '@oxc-minify/binding-openharmony-arm64@0.112.0': + resolution: {integrity: sha512-bLnMojcPadYzMNpB6IAqMiTOag4etc0zbs8On73JsotO1W5c5/j/ncplpSokpEpNasKRUpHVRXpmq0KRXprNhw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-minify/binding-wasm32-wasi@0.102.0': - resolution: {integrity: sha512-52SepCb9e+8cVisGa9S/F14K8PxW0AnbV1j4KEYi8uwfkUIxeDNKRHVHzPoBXNrr0yxW0EHLn/3i8J7a2YCpWw==} + '@oxc-minify/binding-wasm32-wasi@0.112.0': + resolution: {integrity: sha512-tv7PmHYq/8QBlqMaDjsy51GF5KQkG17Yc/PsgB5OVndU34kwbQuebBIic7UfK9ygzidI8moYq3ztnu3za/rqHw==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-minify/binding-win32-arm64-msvc@0.102.0': - resolution: {integrity: sha512-kLs6H1y6sDBKcIimkNwu5th28SLkyvFpHNxdLtCChda0KIGeIXNSiupy5BqEutY+VlWJivKT1OV3Ev3KC5Euzg==} + '@oxc-minify/binding-win32-arm64-msvc@0.112.0': + resolution: {integrity: sha512-d+jes2jwRkcBSpcaZC6cL8GBi56Br6uAorn9dfquhWLczWL+hHSvvVrRgT1i5/6dkf5UWx2zdoEsAMiJ11w78A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-minify/binding-win32-x64-msvc@0.102.0': - resolution: {integrity: sha512-XdyJZdSMN8rbBXH10CrFuU+Q9jIP2+MnxHmNzjK4+bldbTI1UxqwjUMS9bKVC5VCaIEZhh8IE8x4Vf8gmCgrKQ==} + '@oxc-minify/binding-win32-ia32-msvc@0.112.0': + resolution: {integrity: sha512-TV1C3qDwj7//jNIi5tnNRhReSUgtaRQKi5KobDE6zVAc5gjeuBA8G2qizS9ziXlf/I0dlelrGmGMMDJmH9ekWg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-minify/binding-win32-x64-msvc@0.112.0': + resolution: {integrity: sha512-LML2Gld6VY8/+7a3VH4k1qngsBXvTkXgbmYgSYwaElqtiQiYaAcXfi0XKOUGe3k3GbBK4juAGixC31CrdFHAQw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxc-parser/binding-android-arm64@0.102.0': - resolution: {integrity: sha512-pD2if3w3cxPvYbsBSTbhxAYGDaG6WVwnqYG0mYRQ142D6SJ6BpNs7YVQrqpRA2AJQCmzaPP5TRp/koFLebagfQ==} + '@oxc-parser/binding-android-arm-eabi@0.112.0': + resolution: {integrity: sha512-retxBzJ39Da7Lh/eZTn9+HJgTeDUxZIpuI0urOsmcFsBKXAth3lc1jIvwseQ9qbAI/VrsoFOXiGIzgclARbAHg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] + + '@oxc-parser/binding-android-arm64@0.112.0': + resolution: {integrity: sha512-pRkbBRbuIIsufUWpOJ+JHWfJFNupkidy4sbjfcm37e6xwYrn9LSKMLubPHvNaL1Zf92ZRhGiwaYkEcmaFg2VcA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-parser/binding-darwin-arm64@0.102.0': - resolution: {integrity: sha512-RzMN6f6MrjjpQC2Dandyod3iOscofYBpHaTecmoRRbC5sJMwsurkqUMHzoJX9F6IM87kn8m/JcClnoOfx5Sesw==} + '@oxc-parser/binding-darwin-arm64@0.112.0': + resolution: {integrity: sha512-fh6/KQL/cbH5DukT3VkdCqnULLuvVnszVKySD5IgSE0WZb32YZo/cPsPdEv052kk6w3N4agu+NTiMnZjcvhUIg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-parser/binding-darwin-x64@0.102.0': - resolution: {integrity: sha512-Sr2/3K6GEcejY+HgWp5HaxRPzW5XHe9IfGKVn9OhLt8fzVLnXbK5/GjXj7JjMCNKI3G3ZPZDG2Dgm6CX3MaHCA==} + '@oxc-parser/binding-darwin-x64@0.112.0': + resolution: {integrity: sha512-vUBOOY1E30vlu/DoTGDoT1UbLlwu5Yv9tqeBabAwRzwNDz8Skho16VKhsBDUiyqddtpsR3//v6vNk38w4c+6IA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-parser/binding-freebsd-x64@0.102.0': - resolution: {integrity: sha512-s9F2N0KJCGEpuBW6ChpFfR06m2Id9ReaHSl8DCca4HvFNt8SJFPp8fq42n2PZy68rtkremQasM0JDrK2BoBeBQ==} + '@oxc-parser/binding-freebsd-x64@0.112.0': + resolution: {integrity: sha512-hnEtO/9AVnYWzrgnp6L+oPs/6UqlFeteUL6n7magkd2tttgmx1C01hyNNh6nTpZfLzEVJSNJ0S+4NTsK2q2CxA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-parser/binding-linux-arm-gnueabihf@0.102.0': - resolution: {integrity: sha512-zRCIOWzLbqhfY4g8KIZDyYfO2Fl5ltxdQI1v2GlePj66vFWRl8cf4qcBGzxKfsH3wCZHAhmWd1Ht59mnrfH/UQ==} + '@oxc-parser/binding-linux-arm-gnueabihf@0.112.0': + resolution: {integrity: sha512-WxJrUz3pcIc2hp4lvJbvt/sTL33oX9NPvkD3vDDybE6tc0V++rS+hNOJxwXdD2FDIFPkHs/IEn5asEZFVH+VKw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-parser/binding-linux-arm-musleabihf@0.112.0': + resolution: {integrity: sha512-jj8A8WWySaJQqM9XKAIG8U2Q3qxhFQKrXPWv98d1oC35at+L1h+C+V4M3l8BAKhpHKCu3dYlloaAbHd5q1Hw6A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-parser/binding-linux-arm64-gnu@0.102.0': - resolution: {integrity: sha512-5n5RbHgfjulRhKB0pW5p0X/NkQeOpI4uI9WHgIZbORUDATGFC8yeyPA6xYGEs+S3MyEAFxl4v544UEIWwqAgsA==} + '@oxc-parser/binding-linux-arm64-gnu@0.112.0': + resolution: {integrity: sha512-G2F8H6FcAExVK5vvhpSh61tqWx5QoaXXUnSsj5FyuDiFT/K7AMMVSQVqnZREDc+YxhrjB0vnKjCcuobXK63kIw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxc-parser/binding-linux-arm64-musl@0.102.0': - resolution: {integrity: sha512-/XWcmglH/VJ4yKAGTLRgPKSSikh3xciNxkwGiURt8dS30b+3pwc4ZZmudMu0tQ3mjSu0o7V9APZLMpbHK8Bp5w==} + '@oxc-parser/binding-linux-arm64-musl@0.112.0': + resolution: {integrity: sha512-3R0iqjM3xYOZCnwgcxOQXH7hrz64/USDIuLbNTM1kZqQzRqaR4w7SwoWKU934zABo8d0op2oSwOp+CV3hZnM7A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxc-parser/binding-linux-riscv64-gnu@0.102.0': - resolution: {integrity: sha512-2jtIq4nswvy6xdqv1ndWyvVlaRpS0yqomLCvvHdCFx3pFXo5Aoq4RZ39kgvFWrbAtpeYSYeAGFnwgnqjx9ftdw==} + '@oxc-parser/binding-linux-ppc64-gnu@0.112.0': + resolution: {integrity: sha512-lAQf8PQxfgy7h0bmcfSVE3hg3qMueshPYULFsCrHM+8KefGZ9W+ZMvRyU33gLrB4w1O3Fz1orR0hmKMCRxXNrQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-gnu@0.112.0': + resolution: {integrity: sha512-2QlvQBUhHuAE3ezD4X3CAEKMXdfgInggQ5Bj/7gb5NcYP3GyfLTj7c+mMu+BRwfC9B3AXBNyqHWbqEuuUvZyRQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-parser/binding-linux-riscv64-musl@0.112.0': + resolution: {integrity: sha512-v06iu0osHszgqJ1dLQRb6leWFU1sjG/UQk4MoVBtE6ZPewgfTkby6G9II1SpEAf2onnAuQceVYxQH9iuU3NJqw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxc-parser/binding-linux-s390x-gnu@0.102.0': - resolution: {integrity: sha512-Yp6HX/574mvYryiqj0jNvNTJqo4pdAsNP2LPBTxlDQ1cU3lPd7DUA4MQZadaeLI8+AGB2Pn50mPuPyEwFIxeFg==} + '@oxc-parser/binding-linux-s390x-gnu@0.112.0': + resolution: {integrity: sha512-+5HhNHtxsdcd7+ljXFnn9FOoCNXJX3UPgIfIE6vdwS1HqdGNH6eAcVobuqGOp54l8pvcxDQA6F4cPswCgLrQfQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@oxc-parser/binding-linux-x64-gnu@0.102.0': - resolution: {integrity: sha512-R4b0xZpDRhoNB2XZy0kLTSYm0ZmWeKjTii9fcv1Mk3/SIGPrrglwt4U6zEtwK54Dfi4Bve5JnQYduigR/gyDzw==} + '@oxc-parser/binding-linux-x64-gnu@0.112.0': + resolution: {integrity: sha512-jKwO7ZLNkjxwg7FoCLw+fJszooL9yXRZsDN0AQ1AQUTWq1l8GH/2e44k68N3fcP19jl8O8jGpqLAZcQTYk6skA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxc-parser/binding-linux-x64-musl@0.102.0': - resolution: {integrity: sha512-xM5A+03Ti3jvWYZoqaBRS3lusvnvIQjA46Fc9aBE/MHgvKgHSkrGEluLWg/33QEwBwxupkH25Pxc1yu97oZCtg==} + '@oxc-parser/binding-linux-x64-musl@0.112.0': + resolution: {integrity: sha512-TYqnuKV/p3eOc+N61E0961nA7DC+gaCeJ3+V2LcjJdTwFMdikqWL6uVk1jlrpUCBrozHDATVUKDZYH7r4FQYjQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxc-parser/binding-openharmony-arm64@0.102.0': - resolution: {integrity: sha512-AieLlsliblyaTFq7Iw9Nc618tgwV02JT4fQ6VIUd/3ZzbluHIHfPjIXa6Sds+04krw5TvCS8lsegtDYAyzcyhg==} + '@oxc-parser/binding-openharmony-arm64@0.112.0': + resolution: {integrity: sha512-ZhrVmWFifVEFQX4XPwLoVFDHw9tAWH9p9vHsHFH+5uCKdfVR+jje4WxVo6YrokWCboGckoOzHq5KKMOcPZfkRg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-parser/binding-wasm32-wasi@0.102.0': - resolution: {integrity: sha512-w6HRyArs1PBb9rDsQSHlooe31buUlUI2iY8sBzp62jZ1tmvaJo9EIVTQlRNDkwJmk9DF9uEyIJ82EkZcCZTs9A==} + '@oxc-parser/binding-wasm32-wasi@0.112.0': + resolution: {integrity: sha512-Gr8X2PUU3hX1g3F5oLWIZB8DhzDmjr5TfOrmn5tlBOo9l8ojPGdKjnIBfObM7X15928vza8QRKW25RTR7jfivg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-parser/binding-win32-arm64-msvc@0.102.0': - resolution: {integrity: sha512-pqP5UuLiiFONQxqGiUFMdsfybaK1EOK4AXiPlvOvacLaatSEPObZGpyCkAcj9aZcvvNwYdeY9cxGM9IT3togaA==} + '@oxc-parser/binding-win32-arm64-msvc@0.112.0': + resolution: {integrity: sha512-t5CDLbU70Ea88bGRhvU/dLJTc/Wcrtf2Jp534E8P3cgjAvHDjdKsfDDqBZrhybJ8Jv9v9vW5ngE40EK51BluDA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-parser/binding-win32-x64-msvc@0.102.0': - resolution: {integrity: sha512-ntMcL35wuLR1A145rLSmm7m7j8JBZGkROoB9Du0KFIFcfi/w1qk75BdCeiTl3HAKrreAnuhW3QOGs6mJhntowA==} + '@oxc-parser/binding-win32-ia32-msvc@0.112.0': + resolution: {integrity: sha512-rZH0JynCCwnhe2HfRoyNOl/Kfd9pudoWxgpC5OZhj7j77pMK0UOAa35hYDfrtSOUk2HLzrikV5dPUOY2DpSBSA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-parser/binding-win32-x64-msvc@0.112.0': + resolution: {integrity: sha512-oGHluohzmVFAuQrkEnl1OXAxMz2aYmimxUqIgKXpBgbr7PvFv0doELB273sX+5V3fKeggohKg1A2Qq21W9Z9cQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] - '@oxc-project/types@0.102.0': - resolution: {integrity: sha512-8Skrw405g+/UJPKWJ1twIk3BIH2nXdiVlVNtYT23AXVwpsd79es4K+KYt06Fbnkc5BaTvk/COT2JuCLYdwnCdA==} + '@oxc-project/types@0.112.0': + resolution: {integrity: sha512-m6RebKHIRsax2iCwVpYW2ErQwa4ywHJrE4sCK3/8JK8ZZAWOKXaRJFl/uP51gaVyyXlaS4+chU1nSCdzYf6QqQ==} + + '@oxc-transform/binding-android-arm-eabi@0.112.0': + resolution: {integrity: sha512-r4LuBaPnOAi0eUOBNi880Fm2tO2omH7N1FRrL6+nyz/AjQ+QPPLtoyZJva0O+sKi1buyN/7IzM5p9m+5ANSDbg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [android] - '@oxc-transform/binding-android-arm64@0.102.0': - resolution: {integrity: sha512-JLBT7EiExsGmB6LuBBnm6qTfg0rLSxBU+F7xjqy6UXYpL7zhqelGJL7IAq6Pu5UYFT55zVlXXmgzLOXQfpQjXA==} + '@oxc-transform/binding-android-arm64@0.112.0': + resolution: {integrity: sha512-ve46vQcQrY8eGe8990VSlS9gkD+AogJqbtfOkeua+5sQGQTDgeIRRxOm7ktCo19uZc2bEBwXRJITgosd+NRVmQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxc-transform/binding-darwin-arm64@0.102.0': - resolution: {integrity: sha512-xmsBCk/NwE0khy8h6wLEexiS5abCp1ZqJUNHsAovJdGgIW21oGwhiC3VYg1vNLbq+zEXwOHuphVuNEYfBwyNTw==} + '@oxc-transform/binding-darwin-arm64@0.112.0': + resolution: {integrity: sha512-ddbmLU3Tr+i7MOynfwAXxUXud3SjJKlv7XNjaq08qiI8Av/QvhXVGc2bMhXkWQSMSBUeTDoiughKjK+Zsb6y/A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxc-transform/binding-darwin-x64@0.102.0': - resolution: {integrity: sha512-EhBsiq8hSd5BRjlWACB9MxTUiZT2He1s1b3tRP8k3lB8ZTt6sXnDXIWhxRmmM0h//xe6IJ2HuMlbvjXPo/tATg==} + '@oxc-transform/binding-darwin-x64@0.112.0': + resolution: {integrity: sha512-TKvmNw96jQZPqYb4pRrzLFDailNB3YS14KNn+x2hwRbqc6CqY96S9PYwyOpVpYdxfoRjYO9WgX9SoS+62a1DPA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxc-transform/binding-freebsd-x64@0.102.0': - resolution: {integrity: sha512-eujvuYf0x7BFgKyFecbXUa2JBEXT4Ss6vmyrrhVdN07jaeJRiobaKAmeNXBkanoWL2KQLELJbSBgs1ykWYTkzg==} + '@oxc-transform/binding-freebsd-x64@0.112.0': + resolution: {integrity: sha512-YPMkSCDaelO8HHYRMYjm+Q+IfkfIbdtQzwPuasItYkq8UUkNeHNPheNh2JkvQa3c+io3E9ePOgHQ2yihpk7o/Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxc-transform/binding-linux-arm-gnueabihf@0.102.0': - resolution: {integrity: sha512-2x7Ro356PHBVp1SS/dOsHBSnrfs5MlPYwhdKg35t6qixt2bv1kzEH0tDmn4TNEbdjOirmvOXoCTEWUvh8A4f4Q==} + '@oxc-transform/binding-linux-arm-gnueabihf@0.112.0': + resolution: {integrity: sha512-nA7kzQGNEpuTRknst/IJ3l8hqmDmEda3aun6jkXgp7gKxESjuHeaNH04mKISxvJ7fIacvP2g/wtTSnm4u5jL8Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@oxc-transform/binding-linux-arm-musleabihf@0.112.0': + resolution: {integrity: sha512-w8GuLmckKlGc3YujaZKhtbFxziCcosvM2l9GnQjCb/yENWLGDiyQOy0BTAgPGdJwpYTiOeJblEXSuXYvlE1Ong==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxc-transform/binding-linux-arm64-gnu@0.102.0': - resolution: {integrity: sha512-Rz/RbPvT4QwcHKIQ/cOt6Lwl4c7AhK2b6whZfyL6oJ7Uz8UiVl1BCwk8thedrB5h+FEykmaPHoriW1hmBev60g==} + '@oxc-transform/binding-linux-arm64-gnu@0.112.0': + resolution: {integrity: sha512-9LwwGnJ8+WT0rXcrI8M0RJtDNt91eMqcDPPEvJxhRFHIMcHTy5D5xT+fOl3Us0yMqKo3HUWkbfUYqAp4GoZ3Jw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxc-transform/binding-linux-arm64-musl@0.102.0': - resolution: {integrity: sha512-I08iWABrN7zakn3wuNIBWY3hALQGsDLPQbZT1mXws7tyiQqJNGe49uS0/O50QhX3KXj+mbRGsmjVXLXGJE1CVQ==} + '@oxc-transform/binding-linux-arm64-musl@0.112.0': + resolution: {integrity: sha512-Lg6VOuSd3oXv7J0eGywgqh/086h+qQzIBOD+47pYKMTTJcbDe+f3h/RgGoMKJE5HhiwT5sH1aGEJfIfaYUiVSw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] - '@oxc-transform/binding-linux-riscv64-gnu@0.102.0': - resolution: {integrity: sha512-9+SYW1ARAF6Oj/82ayoqKRe8SI7O1qvzs3Y0kijvhIqAaaZWcFRjI5DToyWRAbnzTtHlMcSllZLXNYdmxBjFxA==} + '@oxc-transform/binding-linux-ppc64-gnu@0.112.0': + resolution: {integrity: sha512-PXzmj82o1moA4IGphYImTRgc2youTi4VRfyFX3CHwLjxPcQ5JtcsgbDt4QUdOzXZ+zC07s5jf2ZzhRapEOlj2w==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@oxc-transform/binding-linux-riscv64-gnu@0.112.0': + resolution: {integrity: sha512-vhJsMsVH/6xwa3bt1LGts33FXUkGjaEGDwsRyp4lIfOjSfQVWMtCmWMFNaA0dW9FVWdD2Gt2fSFBSZ+azDxlpg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [riscv64] + os: [linux] + + '@oxc-transform/binding-linux-riscv64-musl@0.112.0': + resolution: {integrity: sha512-cXWFb7z+2IjFUEcXtRwluq9oEG5qnyFCjiu3SWrgYNcWwPdHusv3I/7K5/CTbbi4StoZ5txbi7/iSfDHNyWuRw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] - '@oxc-transform/binding-linux-s390x-gnu@0.102.0': - resolution: {integrity: sha512-HV9nTyQw0TTKYPu+gBhaJBioomiM9O4LcGXi+s5IylCGG6imP0/U13q/9xJnP267QFmiWWqnnSFcv0QAWCyh8A==} + '@oxc-transform/binding-linux-s390x-gnu@0.112.0': + resolution: {integrity: sha512-eEFu4SRqJTJ20/88KRWmp+jpHKAw0Y1DsnSgpEeXyBIIcsOaLIUMU/TfYWUmqRbvbMV9rmOmI3kp5xWYUq6kSQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] - '@oxc-transform/binding-linux-x64-gnu@0.102.0': - resolution: {integrity: sha512-4wcZ08mmdFk8OjsnglyeYGu5PW3TDh87AmcMOi7tZJ3cpJjfzwDfY27KTEUx6G880OpjAiF36OFSPwdKTKgp2g==} + '@oxc-transform/binding-linux-x64-gnu@0.112.0': + resolution: {integrity: sha512-ST1MDT+TlOyZ1c5btrGinRSUW2Jf4Pa+0gdKwsyjDSOC3dxy2ZNkN3mosTf4ywc3J+mxfYKqtjs7zSwHz03ILA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxc-transform/binding-linux-x64-musl@0.102.0': - resolution: {integrity: sha512-rUHZSZBw0FUnUgOhL/Rs7xJz9KjH2eFur/0df6Lwq/isgJc/ggtBtFoZ+y4Fb8ON87a3Y2gS2LT7SEctX0XdPQ==} + '@oxc-transform/binding-linux-x64-musl@0.112.0': + resolution: {integrity: sha512-ISQoA3pD4cyTGpf9sXXeerH6pL2L6EIpdy6oAy2ttkswyVFDyQNVOVIGIdLZDgbpmqGljxZnWqt/J/N68pQaig==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] - '@oxc-transform/binding-openharmony-arm64@0.102.0': - resolution: {integrity: sha512-98y4tccTQ/pA+r2KA/MEJIZ7J8TNTJ4aCT4rX8kWK4pGOko2YsfY3Ru9DVHlLDwmVj7wP8Z4JNxdBrAXRvK+0g==} + '@oxc-transform/binding-openharmony-arm64@0.112.0': + resolution: {integrity: sha512-UOGVrGIv7yLJovyEXEyUTADuLq98vd/cbMHFLJweRXD+11I8Tn4jASi4WzdsN8C3BVYGRHrXH2NlSBmhz33a4g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxc-transform/binding-wasm32-wasi@0.102.0': - resolution: {integrity: sha512-M6myOXxHty3L2TJEB1NlJPtQm0c0LmivAxcGv/+DSDadOoB/UnOUbjM8W2Utlh5IYS9ARSOjqHtBiPYLWJ15XA==} + '@oxc-transform/binding-wasm32-wasi@0.112.0': + resolution: {integrity: sha512-XIX7Gpq9koAvzBVHDlVFHM79r5uOVK6kTEsdsN4qaajpjkgtv4tdsAOKIYK6l7fUbsbE6xS+6w1+yRFrDeC1kg==} engines: {node: '>=14.0.0'} cpu: [wasm32] - '@oxc-transform/binding-win32-arm64-msvc@0.102.0': - resolution: {integrity: sha512-jzaA1lLiMXiJs4r7E0BHRxTPiwAkpoCfSNRr8npK/SqL4UQE4cSz3WDTX5wJWRrN2U+xqsDGefeYzH4reI8sgw==} + '@oxc-transform/binding-win32-arm64-msvc@0.112.0': + resolution: {integrity: sha512-EgXef9kOne9BNsbYBbuRqxk2hteT0xsAGcx/VbtCBMJYNj8fANFhT271DUSOgfa4DAgrQQmsyt/Kr1aV9mpU9w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxc-transform/binding-win32-x64-msvc@0.102.0': - resolution: {integrity: sha512-eYOm6mch+1cP9qlNkMdorfBFY8aEOxY/isqrreLmEWqF/hyXA0SbLKDigTbvh3JFKny/gXlHoCKckqfua4cwtg==} + '@oxc-transform/binding-win32-ia32-msvc@0.112.0': + resolution: {integrity: sha512-6QaB0qjNaou2YR+blncHdw7j0e26IOwOIjLbhVGDeuf9+4rjJeiqRXJ2hOtCcS4zblnao/MjdgQuZ3fM0nl+Kw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ia32] + os: [win32] + + '@oxc-transform/binding-win32-x64-msvc@0.112.0': + resolution: {integrity: sha512-FRKYlY959QeqRPx9kXs0HjU2xuXPT1cdF+vvA200D9uAX/KLcC34MwRqUKTYml4kCc2Vf/P2pBR9cQuBm3zECQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -2932,23 +3040,23 @@ packages: '@prisma/get-platform@6.19.0': resolution: {integrity: sha512-ym85WDO2yDhC3fIXHWYpG3kVMBA49cL1XD2GCsCF8xbwoy2OkDQY44gEbAt2X46IQ4Apq9H6g0Ex1iFfPqEkHA==} - '@rolldown/pluginutils@1.0.0-beta.53': - resolution: {integrity: sha512-vENRlFU4YbrwVqNDZ7fLvy+JR1CRkyr01jhSiDpE1u6py3OMzQfztQU2jxykW3ALNxO4kSlqIDeYyD0Y9RcQeQ==} + '@rolldown/pluginutils@1.0.0-rc.2': + resolution: {integrity: sha512-izyXV/v+cHiRfozX62W9htOAvwMo4/bXKDrQ+vom1L1qRuexPock/7VZDAhnpHCLNejd3NJ6hiab+tO0D44Rgw==} - '@rolldown/pluginutils@1.0.0-beta.57': - resolution: {integrity: sha512-aQNelgx14tGA+n2tNSa9x6/jeoCL9fkDeCei7nOKnHx0fEFRRMu5ReiITo+zZD5TzWDGGRjbSYCs93IfRIyTuQ==} + '@rolldown/pluginutils@1.0.0-rc.5': + resolution: {integrity: sha512-RxlLX/DPoarZ9PtxVrQgZhPoor987YtKQqCo5zkjX+0S0yLJ7Vv515Wk6+xtTL67VONKJKxETWZwuZjss2idYw==} - '@rollup/plugin-alias@5.1.1': - resolution: {integrity: sha512-PR9zDb+rOzkRb2VD+EuKB7UC41vU5DIwZ5qqCpk0KJudcWAyi8rvYOhS7+L5aZCspw1stTViLgN5v6FF1p5cgQ==} - engines: {node: '>=14.0.0'} + '@rollup/plugin-alias@6.0.0': + resolution: {integrity: sha512-tPCzJOtS7uuVZd+xPhoy5W4vThe6KWXNmsFCNktaAh5RTqcLiSfT4huPQIXkgJ6YCOjJHvecOAzQxLFhPxKr+g==} + engines: {node: '>=20.19.0'} peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + rollup: '>=4.0.0' peerDependenciesMeta: rollup: optional: true - '@rollup/plugin-commonjs@28.0.9': - resolution: {integrity: sha512-PIR4/OHZ79romx0BVVll/PkwWpJ7e5lsqFa3gFfcrFPWwLXLV39JVUzQV9RKjWerE7B845Hqjj9VYlQeieZ2dA==} + '@rollup/plugin-commonjs@29.0.0': + resolution: {integrity: sha512-U2YHaxR2cU/yAiwKJtJRhnyLk7cifnQw0zUpISsocBDoHDJn+HTV74ABqnwr5bEgWUwFZC9oFL6wLe21lHu5eQ==} engines: {node: '>=16.0.0 || 14 >= 14.17'} peerDependencies: rollup: ^2.68.0||^3.0.0||^4.0.0 @@ -3010,213 +3118,128 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.44.0': - resolution: {integrity: sha512-xEiEE5oDW6tK4jXCAyliuntGR+amEMO7HLtdSshVuhFnKTYoeYMyXQK7pLouAJJj5KHdwdn87bfHAR2nSdNAUA==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm-eabi@4.52.5': - resolution: {integrity: sha512-8c1vW4ocv3UOMp9K+gToY5zL2XiiVw3k7f1ksf4yO1FlDFQ1C2u72iACFnSOceJFsWskc2WZNqeRhFRPzv+wtQ==} + '@rollup/rollup-android-arm-eabi@4.59.0': + resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.44.0': - resolution: {integrity: sha512-uNSk/TgvMbskcHxXYHzqwiyBlJ/lGcv8DaUfcnNwict8ba9GTTNxfn3/FAoFZYgkaXXAdrAA+SLyKplyi349Jw==} + '@rollup/rollup-android-arm64@4.59.0': + resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==} cpu: [arm64] os: [android] - '@rollup/rollup-android-arm64@4.52.5': - resolution: {integrity: sha512-mQGfsIEFcu21mvqkEKKu2dYmtuSZOBMmAl5CFlPGLY94Vlcm+zWApK7F/eocsNzp8tKmbeBP8yXyAbx0XHsFNA==} + '@rollup/rollup-darwin-arm64@4.59.0': + resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==} cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.44.0': - resolution: {integrity: sha512-VGF3wy0Eq1gcEIkSCr8Ke03CWT+Pm2yveKLaDvq51pPpZza3JX/ClxXOCmTYYq3us5MvEuNRTaeyFThCKRQhOA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-arm64@4.52.5': - resolution: {integrity: sha512-takF3CR71mCAGA+v794QUZ0b6ZSrgJkArC+gUiG6LB6TQty9T0Mqh3m2ImRBOxS2IeYBo4lKWIieSvnEk2OQWA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.44.0': - resolution: {integrity: sha512-fBkyrDhwquRvrTxSGH/qqt3/T0w5Rg0L7ZIDypvBPc1/gzjJle6acCpZ36blwuwcKD/u6oCE/sRWlUAcxLWQbQ==} - cpu: [x64] os: [darwin] - '@rollup/rollup-darwin-x64@4.52.5': - resolution: {integrity: sha512-W901Pla8Ya95WpxDn//VF9K9u2JbocwV/v75TE0YIHNTbhqUTv9w4VuQ9MaWlNOkkEfFwkdNhXgcLqPSmHy0fA==} + '@rollup/rollup-darwin-x64@4.59.0': + resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==} cpu: [x64] os: [darwin] - '@rollup/rollup-freebsd-arm64@4.44.0': - resolution: {integrity: sha512-u5AZzdQJYJXByB8giQ+r4VyfZP+walV+xHWdaFx/1VxsOn6eWJhK2Vl2eElvDJFKQBo/hcYIBg/jaKS8ZmKeNQ==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-arm64@4.52.5': - resolution: {integrity: sha512-QofO7i7JycsYOWxe0GFqhLmF6l1TqBswJMvICnRUjqCx8b47MTo46W8AoeQwiokAx3zVryVnxtBMcGcnX12LvA==} + '@rollup/rollup-freebsd-arm64@4.59.0': + resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==} cpu: [arm64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.44.0': - resolution: {integrity: sha512-qC0kS48c/s3EtdArkimctY7h3nHicQeEUdjJzYVJYR3ct3kWSafmn6jkNCA8InbUdge6PVx6keqjk5lVGJf99g==} + '@rollup/rollup-freebsd-x64@4.59.0': + resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==} cpu: [x64] os: [freebsd] - '@rollup/rollup-freebsd-x64@4.52.5': - resolution: {integrity: sha512-jr21b/99ew8ujZubPo9skbrItHEIE50WdV86cdSoRkKtmWa+DDr6fu2c/xyRT0F/WazZpam6kk7IHBerSL7LDQ==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.44.0': - resolution: {integrity: sha512-x+e/Z9H0RAWckn4V2OZZl6EmV0L2diuX3QB0uM1r6BvhUIv6xBPL5mrAX2E3e8N8rEHVPwFfz/ETUbV4oW9+lQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': - resolution: {integrity: sha512-PsNAbcyv9CcecAUagQefwX8fQn9LQ4nZkpDboBOttmyffnInRy8R8dSg6hxxl2Re5QhHBf6FYIDhIj5v982ATQ==} - cpu: [arm] - os: [linux] - - '@rollup/rollup-linux-arm-musleabihf@4.44.0': - resolution: {integrity: sha512-1exwiBFf4PU/8HvI8s80icyCcnAIB86MCBdst51fwFmH5dyeoWVPVgmQPcKrMtBQ0W5pAs7jBCWuRXgEpRzSCg==} + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': + resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.52.5': - resolution: {integrity: sha512-Fw4tysRutyQc/wwkmcyoqFtJhh0u31K+Q6jYjeicsGJJ7bbEq8LwPWV/w0cnzOqR2m694/Af6hpFayLJZkG2VQ==} + '@rollup/rollup-linux-arm-musleabihf@4.59.0': + resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.44.0': - resolution: {integrity: sha512-ZTR2mxBHb4tK4wGf9b8SYg0Y6KQPjGpR4UWwTFdnmjB4qRtoATZ5dWn3KsDwGa5Z2ZBOE7K52L36J9LueKBdOQ==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-arm64-gnu@4.52.5': - resolution: {integrity: sha512-a+3wVnAYdQClOTlyapKmyI6BLPAFYs0JM8HRpgYZQO02rMR09ZcV9LbQB+NL6sljzG38869YqThrRnfPMCDtZg==} + '@rollup/rollup-linux-arm64-gnu@4.59.0': + resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.44.0': - resolution: {integrity: sha512-GFWfAhVhWGd4r6UxmnKRTBwP1qmModHtd5gkraeW2G490BpFOZkFtem8yuX2NyafIP/mGpRJgTJ2PwohQkUY/Q==} + '@rollup/rollup-linux-arm64-musl@4.59.0': + resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.52.5': - resolution: {integrity: sha512-AvttBOMwO9Pcuuf7m9PkC1PUIKsfaAJ4AYhy944qeTJgQOqJYJ9oVl2nYgY7Rk0mkbsuOpCAYSs6wLYB2Xiw0Q==} - cpu: [arm64] - os: [linux] - - '@rollup/rollup-linux-loong64-gnu@4.52.5': - resolution: {integrity: sha512-DkDk8pmXQV2wVrF6oq5tONK6UHLz/XcEVow4JTTerdeV1uqPeHxwcg7aFsfnSm9L+OO8WJsWotKM2JJPMWrQtA==} + '@rollup/rollup-linux-loong64-gnu@4.59.0': + resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-loongarch64-gnu@4.44.0': - resolution: {integrity: sha512-xw+FTGcov/ejdusVOqKgMGW3c4+AgqrfvzWEVXcNP6zq2ue+lsYUgJ+5Rtn/OTJf7e2CbgTFvzLW2j0YAtj0Gg==} + '@rollup/rollup-linux-loong64-musl@4.59.0': + resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==} cpu: [loong64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': - resolution: {integrity: sha512-bKGibTr9IdF0zr21kMvkZT4K6NV+jjRnBoVMt2uNMG0BYWm3qOVmYnXKzx7UhwrviKnmK46IKMByMgvpdQlyJQ==} + '@rollup/rollup-linux-ppc64-gnu@4.59.0': + resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-ppc64-gnu@4.52.5': - resolution: {integrity: sha512-W/b9ZN/U9+hPQVvlGwjzi+Wy4xdoH2I8EjaCkMvzpI7wJUs8sWJ03Rq96jRnHkSrcHTpQe8h5Tg3ZzUPGauvAw==} + '@rollup/rollup-linux-ppc64-musl@4.59.0': + resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.44.0': - resolution: {integrity: sha512-vV3cL48U5kDaKZtXrti12YRa7TyxgKAIDoYdqSIOMOFBXqFj2XbChHAtXquEn2+n78ciFgr4KIqEbydEGPxXgA==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-gnu@4.52.5': - resolution: {integrity: sha512-sjQLr9BW7R/ZiXnQiWPkErNfLMkkWIoCz7YMn27HldKsADEKa5WYdobaa1hmN6slu9oWQbB6/jFpJ+P2IkVrmw==} - cpu: [riscv64] - os: [linux] - - '@rollup/rollup-linux-riscv64-musl@4.44.0': - resolution: {integrity: sha512-TDKO8KlHJuvTEdfw5YYFBjhFts2TR0VpZsnLLSYmB7AaohJhM8ctDSdDnUGq77hUh4m/djRafw+9zQpkOanE2Q==} + '@rollup/rollup-linux-riscv64-gnu@4.59.0': + resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-riscv64-musl@4.52.5': - resolution: {integrity: sha512-hq3jU/kGyjXWTvAh2awn8oHroCbrPm8JqM7RUpKjalIRWWXE01CQOf/tUNWNHjmbMHg/hmNCwc/Pz3k1T/j/Lg==} + '@rollup/rollup-linux-riscv64-musl@4.59.0': + resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.44.0': - resolution: {integrity: sha512-8541GEyktXaw4lvnGp9m84KENcxInhAt6vPWJ9RodsB/iGjHoMB2Pp5MVBCiKIRxrxzJhGCxmNzdu+oDQ7kwRA==} - cpu: [s390x] - os: [linux] - - '@rollup/rollup-linux-s390x-gnu@4.52.5': - resolution: {integrity: sha512-gn8kHOrku8D4NGHMK1Y7NA7INQTRdVOntt1OCYypZPRt6skGbddska44K8iocdpxHTMMNui5oH4elPH4QOLrFQ==} + '@rollup/rollup-linux-s390x-gnu@4.59.0': + resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.44.0': - resolution: {integrity: sha512-iUVJc3c0o8l9Sa/qlDL2Z9UP92UZZW1+EmQ4xfjTc1akr0iUFZNfxrXJ/R1T90h/ILm9iXEY6+iPrmYB3pXKjw==} + '@rollup/rollup-linux-x64-gnu@4.59.0': + resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.52.5': - resolution: {integrity: sha512-hXGLYpdhiNElzN770+H2nlx+jRog8TyynpTVzdlc6bndktjKWyZyiCsuDAlpd+j+W+WNqfcyAWz9HxxIGfZm1Q==} + '@rollup/rollup-linux-x64-musl@4.59.0': + resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.44.0': - resolution: {integrity: sha512-PQUobbhLTQT5yz/SPg116VJBgz+XOtXt8D1ck+sfJJhuEsMj2jSej5yTdp8CvWBSceu+WW+ibVL6dm0ptG5fcA==} + '@rollup/rollup-openbsd-x64@4.59.0': + resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==} cpu: [x64] - os: [linux] - - '@rollup/rollup-linux-x64-musl@4.52.5': - resolution: {integrity: sha512-arCGIcuNKjBoKAXD+y7XomR9gY6Mw7HnFBv5Rw7wQRvwYLR7gBAgV7Mb2QTyjXfTveBNFAtPt46/36vV9STLNg==} - cpu: [x64] - os: [linux] + os: [openbsd] - '@rollup/rollup-openharmony-arm64@4.52.5': - resolution: {integrity: sha512-QoFqB6+/9Rly/RiPjaomPLmR/13cgkIGfA40LHly9zcH1S0bN2HVFYk3a1eAyHQyjs3ZJYlXvIGtcCs5tko9Cw==} + '@rollup/rollup-openharmony-arm64@4.59.0': + resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==} cpu: [arm64] os: [openharmony] - '@rollup/rollup-win32-arm64-msvc@4.44.0': - resolution: {integrity: sha512-M0CpcHf8TWn+4oTxJfh7LQuTuaYeXGbk0eageVjQCKzYLsajWS/lFC94qlRqOlyC2KvRT90ZrfXULYmukeIy7w==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-arm64-msvc@4.52.5': - resolution: {integrity: sha512-w0cDWVR6MlTstla1cIfOGyl8+qb93FlAVutcor14Gf5Md5ap5ySfQ7R9S/NjNaMLSFdUnKGEasmVnu3lCMqB7w==} + '@rollup/rollup-win32-arm64-msvc@4.59.0': + resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.44.0': - resolution: {integrity: sha512-3XJ0NQtMAXTWFW8FqZKcw3gOQwBtVWP/u8TpHP3CRPXD7Pd6s8lLdH3sHWh8vqKCyyiI8xW5ltJScQmBU9j7WA==} + '@rollup/rollup-win32-ia32-msvc@4.59.0': + resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.52.5': - resolution: {integrity: sha512-Aufdpzp7DpOTULJCuvzqcItSGDH73pF3ko/f+ckJhxQyHtp67rHw3HMNxoIdDMUITJESNE6a8uh4Lo4SLouOUg==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.52.5': - resolution: {integrity: sha512-UGBUGPFp1vkj6p8wCRraqNhqwX/4kNQPS57BCFc8wYh0g94iVIW33wJtQAx3G7vrjjNtRaxiMUylM0ktp/TRSQ==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.44.0': - resolution: {integrity: sha512-Q2Mgwt+D8hd5FIPUuPDsvPR7Bguza6yTkJxspDGkZj7tBRn2y4KSWYuIXpftFSjBra76TbKerCV7rgFPQrn+wQ==} + '@rollup/rollup-win32-x64-gnu@4.59.0': + resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==} cpu: [x64] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.52.5': - resolution: {integrity: sha512-TAcgQh2sSkykPRWLrdyy2AiceMckNf5loITqXxFI5VuQjS5tSuw3WlwdN8qv8vzjLAUTvYaH/mVjSFpbkFbpTg==} + '@rollup/rollup-win32-x64-msvc@4.59.0': + resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==} cpu: [x64] os: [win32] @@ -3243,11 +3266,6 @@ packages: '@standard-schema/spec@1.0.0': resolution: {integrity: sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA==} - '@sveltejs/acorn-typescript@1.0.6': - resolution: {integrity: sha512-4awhxtMh4cx9blePWl10HRHj8Iivtqj+2QdDCSMDzxG+XKa9+VCNupQuCuvzEhYPzZSrX+0gC+0lHA/0fFKKQQ==} - peerDependencies: - acorn: ^8.9.0 - '@sveltejs/acorn-typescript@1.0.9': resolution: {integrity: sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==} peerDependencies: @@ -3258,30 +3276,20 @@ packages: peerDependencies: '@sveltejs/kit': ^2.0.0 - '@sveltejs/kit@2.48.3': - resolution: {integrity: sha512-jf8mx3yctRXE9hvixgcqqK94YI2hDnbxI/12Upkz99XFMvxnJKCMzvz0j7lmbXSyBSNEycWO5xHvi7b73y9qkQ==} + '@sveltejs/kit@2.53.2': + resolution: {integrity: sha512-M+MqAvFve12T1HWws/2npP/s3hFtyjw3GB/OXW/8a1jZBk48qnvPJrtgE+VOMc3RnjUMxc4mv/vQ73nvj2uNMg==} engines: {node: '>=18.13'} hasBin: true peerDependencies: '@opentelemetry/api': ^1.0.0 - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 + '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 || ^7.0.0 svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 + typescript: ^5.3.3 + vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 || ^8.0.0 peerDependenciesMeta: '@opentelemetry/api': optional: true - - '@sveltejs/kit@2.49.1': - resolution: {integrity: sha512-vByReCTTdlNM80vva8alAQC80HcOiHLkd8XAxIiKghKSHcqeNfyhp3VsYAV8VSiPKu4Jc8wWCfsZNAIvd1uCqA==} - engines: {node: '>=18.13'} - hasBin: true - peerDependencies: - '@opentelemetry/api': ^1.0.0 - '@sveltejs/vite-plugin-svelte': ^3.0.0 || ^4.0.0-next.1 || ^5.0.0 || ^6.0.0-next.0 - svelte: ^4.0.0 || ^5.0.0-next.0 - vite: ^5.0.3 || ^6.0.0 || ^7.0.0-beta.0 - peerDependenciesMeta: - '@opentelemetry/api': + typescript: optional: true '@sveltejs/package@2.5.7': @@ -3680,10 +3688,6 @@ packages: '@types/node@20.19.24': resolution: {integrity: sha512-FE5u0ezmi6y9OZEzlJfg37mqqf6ZDSF2V/NLjUyGrR9uTZ7Sb9F7bLNZ03S4XVUNRWGA7Ck4c1kK+YnuWjl+DA==} - '@types/parse-path@7.1.0': - resolution: {integrity: sha512-EULJ8LApcVEPbrfND0cRQqutIOdiIgJ1Mgrhpy755r14xMohPTEpkV/k28SJvuOs9bHRFW8x+KeDAEPiGQPB9Q==} - deprecated: This is a stub types definition. parse-path provides its own type definitions, so you do not need this installed. - '@types/pg@8.11.11': resolution: {integrity: sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==} @@ -3740,6 +3744,9 @@ packages: '@types/toposort@2.0.7': resolution: {integrity: sha512-sQNk65vbC36+UixCkcky+dCr7MlflHcVILg1FVGqlUntsLFv9xd9ToWIVko/gTuin+cVe16t+2YubEFkhnSuPQ==} + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + '@types/vscode@1.101.0': resolution: {integrity: sha512-ZWf0IWa+NGegdW3iU42AcDTFHWW7fApLdkdnBqwYEtHVIBGbTu0ZNQKP/kX3Ds/uMJXIMQNAojHR4vexCEEz5Q==} @@ -3864,8 +3871,8 @@ packages: resolution: {integrity: sha512-tUFMXI4gxzzMXt4xpGJEsBsTox0XbNQ1y94EwlD/CuZwFcQP79xfQqMhau9HsRc/J0cAPA/HZt1dZPtGn9V/7w==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@unhead/vue@2.0.19': - resolution: {integrity: sha512-7BYjHfOaoZ9+ARJkT10Q2TjnTUqDXmMpfakIAsD/hXiuff1oqWg1xeXT5+MomhNcC15HbiABpbbBmITLSHxdKg==} + '@unhead/vue@2.1.7': + resolution: {integrity: sha512-azD4x32BKBDMcKMcCeU+kCBowPFRFSSFJSHP+mr2P1TpPV/4b6UsmMPiTb+gSW8NZ6s0myJ/CaVMoeC5mGKhKg==} peerDependencies: vue: '>=3.5.18' @@ -3964,20 +3971,20 @@ packages: cpu: [x64] os: [win32] - '@vercel/nft@0.30.3': - resolution: {integrity: sha512-UEq+eF0ocEf9WQCV1gktxKhha36KDs7jln5qii6UpPf5clMqDc0p3E7d9l2Smx0i9Pm1qpq4S4lLfNl97bbv6w==} - engines: {node: '>=18'} + '@vercel/nft@1.3.2': + resolution: {integrity: sha512-HC8venRc4Ya7vNeBsJneKHHMDDWpQie7VaKhAIOst3MKO+DES+Y/SbzSp8mFkD7OzwAE2HhHkeSuSmwS20mz3A==} + engines: {node: '>=20'} hasBin: true - '@vitejs/plugin-vue-jsx@5.1.3': - resolution: {integrity: sha512-I6Zr8cYVr5WHMW5gNOP09DNqW9rgO8RX73Wa6Czgq/0ndpTfJM4vfDChfOT1+3KtdrNqilNBtNlFwVeB02ZzGw==} + '@vitejs/plugin-vue-jsx@5.1.4': + resolution: {integrity: sha512-70LmoVk9riR7qc4W2CpjsbNMWTPnuZb9dpFKX1emru0yP57nsc9k8nhLA6U93ngQapv5VDIUq2JatNfLbBIkrA==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 vue: ^3.0.0 - '@vitejs/plugin-vue@6.0.3': - resolution: {integrity: sha512-TlGPkLFLVOY3T7fZrwdvKpjprR3s4fxRln0ORDo1VQ7HHyxJwTlrjKU3kpVWTlaAjIEuCTokmjkZnr8Tpc925w==} + '@vitejs/plugin-vue@6.0.4': + resolution: {integrity: sha512-uM5iXipgYIn13UUQCZNdWkYk+sysBeA97d5mHsAoAt1u/wpN3+zxOmsVJWosuzX+IMGRzeYUNytztrYznboIkQ==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 @@ -4027,15 +4034,9 @@ packages: '@vitest/utils@4.0.16': resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} - '@volar/language-core@2.4.27': - resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} - '@volar/language-core@2.4.28': resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} - '@volar/source-map@2.4.27': - resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} - '@volar/source-map@2.4.28': resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} @@ -4073,24 +4074,36 @@ packages: '@vue/compiler-core@3.5.26': resolution: {integrity: sha512-vXyI5GMfuoBCnv5ucIT7jhHKl55Y477yxP6fc4eUswjP8FG3FFVFd41eNDArR+Uk3QKn2Z85NavjaxLxOC19/w==} + '@vue/compiler-core@3.5.29': + resolution: {integrity: sha512-cuzPhD8fwRHk8IGfmYaR4eEe4cAyJEL66Ove/WZL7yWNL134nqLddSLwNRIsFlnnW1kK+p8Ck3viFnC0chXCXw==} + '@vue/compiler-dom@3.5.22': resolution: {integrity: sha512-W8RknzUM1BLkypvdz10OVsGxnMAuSIZs9Wdx1vzA3mL5fNMN15rhrSCLiTm6blWeACwUwizzPVqGJgOGBEN/hA==} '@vue/compiler-dom@3.5.26': resolution: {integrity: sha512-y1Tcd3eXs834QjswshSilCBnKGeQjQXB6PqFn/1nxcQw4pmG42G8lwz+FZPAZAby6gZeHSt/8LMPfZ4Rb+Bd/A==} + '@vue/compiler-dom@3.5.29': + resolution: {integrity: sha512-n0G5o7R3uBVmVxjTIYcz7ovr8sy7QObFG8OQJ3xGCDNhbG60biP/P5KnyY8NLd81OuT1WJflG7N4KWYHaeeaIg==} + '@vue/compiler-sfc@3.5.22': resolution: {integrity: sha512-tbTR1zKGce4Lj+JLzFXDq36K4vcSZbJ1RBu8FxcDv1IGRz//Dh2EBqksyGVypz3kXpshIfWKGOCcqpSbyGWRJQ==} '@vue/compiler-sfc@3.5.26': resolution: {integrity: sha512-egp69qDTSEZcf4bGOSsprUr4xI73wfrY5oRs6GSgXFTiHrWj4Y3X5Ydtip9QMqiCMCPVwLglB9GBxXtTadJ3mA==} + '@vue/compiler-sfc@3.5.29': + resolution: {integrity: sha512-oJZhN5XJs35Gzr50E82jg2cYdZQ78wEwvRO6Y63TvLVTc+6xICzJHP1UIecdSPPYIbkautNBanDiWYa64QSFIA==} + '@vue/compiler-ssr@3.5.22': resolution: {integrity: sha512-GdgyLvg4R+7T8Nk2Mlighx7XGxq/fJf9jaVofc3IL0EPesTE86cP/8DD1lT3h1JeZr2ySBvyqKQJgbS54IX1Ww==} '@vue/compiler-ssr@3.5.26': resolution: {integrity: sha512-lZT9/Y0nSIRUPVvapFJEVDbEXruZh2IYHMk2zTtEgJSlP5gVOqeWXH54xDKAaFS4rTnDeDBQUYDtxKyoW9FwDw==} + '@vue/compiler-ssr@3.5.29': + resolution: {integrity: sha512-Y/ARJZE6fpjzL5GH/phJmsFwx3g6t2KmHKHx5q+MLl2kencADKIrhH5MLF6HHpRMmlRAYBRSvv347Mepf1zVNw==} + '@vue/devtools-api@6.6.4': resolution: {integrity: sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==} @@ -4105,39 +4118,36 @@ packages: '@vue/devtools-shared@8.0.5': resolution: {integrity: sha512-bRLn6/spxpmgLk+iwOrR29KrYnJjG9DGpHGkDFG82UM21ZpJ39ztUT9OXX3g+usW7/b2z+h46I9ZiYyB07XMXg==} - '@vue/language-core@3.2.1': - resolution: {integrity: sha512-g6oSenpnGMtpxHGAwKuu7HJJkNZpemK/zg3vZzZbJ6cnnXq1ssxuNrXSsAHYM3NvH8p4IkTw+NLmuxyeYz4r8A==} - '@vue/language-core@3.2.5': resolution: {integrity: sha512-d3OIxN/+KRedeM5wQ6H6NIpwS3P5gC9nmyaHgBk+rO6dIsjY+tOh4UlPpiZbAh3YtLdCGEX4M16RmsBqPmJV+g==} '@vue/reactivity@3.5.22': resolution: {integrity: sha512-f2Wux4v/Z2pqc9+4SmgZC1p73Z53fyD90NFWXiX9AKVnVBEvLFOWCEgJD3GdGnlxPZt01PSlfmLqbLYzY/Fw4A==} - '@vue/reactivity@3.5.26': - resolution: {integrity: sha512-9EnYB1/DIiUYYnzlnUBgwU32NNvLp/nhxLXeWRhHUEeWNTn1ECxX8aGO7RTXeX6PPcxe3LLuNBFoJbV4QZ+CFQ==} + '@vue/reactivity@3.5.29': + resolution: {integrity: sha512-zcrANcrRdcLtmGZETBxWqIkoQei8HaFpZWx/GHKxx79JZsiZ8j1du0VUJtu4eJjgFvU/iKL5lRXFXksVmI+5DA==} '@vue/runtime-core@3.5.22': resolution: {integrity: sha512-EHo4W/eiYeAzRTN5PCextDUZ0dMs9I8mQ2Fy+OkzvRPUYQEyK9yAjbasrMCXbLNhF7P0OUyivLjIy0yc6VrLJQ==} - '@vue/runtime-core@3.5.26': - resolution: {integrity: sha512-xJWM9KH1kd201w5DvMDOwDHYhrdPTrAatn56oB/LRG4plEQeZRQLw0Bpwih9KYoqmzaxF0OKSn6swzYi84e1/Q==} + '@vue/runtime-core@3.5.29': + resolution: {integrity: sha512-8DpW2QfdwIWOLqtsNcds4s+QgwSaHSJY/SUe04LptianUQ/0xi6KVsu/pYVh+HO3NTVvVJjIPL2t6GdeKbS4Lg==} '@vue/runtime-dom@3.5.22': resolution: {integrity: sha512-Av60jsryAkI023PlN7LsqrfPvwfxOd2yAwtReCjeuugTJTkgrksYJJstg1e12qle0NarkfhfFu1ox2D+cQotww==} - '@vue/runtime-dom@3.5.26': - resolution: {integrity: sha512-XLLd/+4sPC2ZkN/6+V4O4gjJu6kSDbHAChvsyWgm1oGbdSO3efvGYnm25yCjtFm/K7rrSDvSfPDgN1pHgS4VNQ==} + '@vue/runtime-dom@3.5.29': + resolution: {integrity: sha512-AHvvJEtcY9tw/uk+s/YRLSlxxQnqnAkjqvK25ZiM4CllCZWzElRAoQnCM42m9AHRLNJ6oe2kC5DCgD4AUdlvXg==} '@vue/server-renderer@3.5.22': resolution: {integrity: sha512-gXjo+ao0oHYTSswF+a3KRHZ1WszxIqO7u6XwNHqcqb9JfyIL/pbWrrh/xLv7jeDqla9u+LK7yfZKHih1e1RKAQ==} peerDependencies: vue: 3.5.22 - '@vue/server-renderer@3.5.26': - resolution: {integrity: sha512-TYKLXmrwWKSodyVuO1WAubucd+1XlLg4set0YoV+Hu8Lo79mp/YMwWV5mC5FgtsDxX3qo1ONrxFaTP1OQgy1uA==} + '@vue/server-renderer@3.5.29': + resolution: {integrity: sha512-G/1k6WK5MusLlbxSE2YTcqAAezS+VuwHhOvLx2KnQU7G2zCH6KIb+5Wyt6UjMq7a3qPzNEjJXs1hvAxDclQH+g==} peerDependencies: - vue: 3.5.26 + vue: 3.5.29 '@vue/shared@3.5.22': resolution: {integrity: sha512-F4yc6palwq3TT0u+FYf0Ns4Tfl9GRFURDN2gWG7L1ecIaS/4fCIuFOjMTnCyjsu/OK6vaDKLCrGAa+KvvH+h4w==} @@ -4145,6 +4155,9 @@ packages: '@vue/shared@3.5.26': resolution: {integrity: sha512-7Z6/y3uFI5PRoKeorTOSXKcDj0MSasfNNltcslbFrPpcw6aXRUALq4IfJlaTRspiWIUOEZbrpM+iQGmCOiWe4A==} + '@vue/shared@3.5.29': + resolution: {integrity: sha512-w7SR0A5zyRByL9XUkCfdLs7t9XOHUyJ67qPGQjOou3p6GvBeBW+AVjUUmlxtZ4PIYaRvE+1LmK44O4uajlZwcg==} + abbrev@3.0.1: resolution: {integrity: sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==} engines: {node: ^18.17.0 || >=20.5.0} @@ -4191,11 +4204,11 @@ packages: ajv: optional: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - ajv@8.17.1: - resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} alien-signals@3.0.3: resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==} @@ -4249,6 +4262,10 @@ packages: aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + aria-query@5.3.1: + resolution: {integrity: sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==} + engines: {node: '>= 0.4'} + aria-query@5.3.2: resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} engines: {node: '>= 0.4'} @@ -4319,8 +4336,8 @@ packages: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} - autoprefixer@10.4.23: - resolution: {integrity: sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==} + autoprefixer@10.4.27: + resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} engines: {node: ^10 || ^12 || >=14} hasBin: true peerDependencies: @@ -4356,6 +4373,10 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + bare-events@2.8.1: resolution: {integrity: sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==} peerDependencies: @@ -4375,8 +4396,8 @@ packages: resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} hasBin: true - better-auth@1.4.17: - resolution: {integrity: sha512-VmHGQyKsEahkEs37qguROKg/6ypYpNF13D7v/lkbO7w7Aivz0Bv2h+VyUkH4NzrGY0QBKXi1577mGhDCVwp0ew==} + better-auth@1.4.19: + resolution: {integrity: sha512-3RlZJcA0+NH25wYD85vpIGwW9oSTuEmLIaGbT8zg41w/Pa2hVWHKedjoUHHJtnzkBXzDb+CShkLnSw7IThDdqQ==} peerDependencies: '@lynx-js/react': '*' '@prisma/client': ^5.0.0 || ^6.0.0 || ^7.0.0 @@ -4461,8 +4482,8 @@ packages: bl@4.1.0: resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - body-parser@2.2.0: - resolution: {integrity: sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==} + body-parser@2.2.2: + resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} boolbase@1.0.0: @@ -4471,12 +4492,13 @@ packages: brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.1: - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} - brace-expansion@2.0.2: resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@5.0.3: + resolution: {integrity: sha512-fy6KJm2RawA5RcHkLa1z/ScpBeA762UF9KmZQxwIbDtRJrgLzM10depAiEQ+CXYcoiqW1/m96OAAoke2nE9EeA==} + engines: {node: 18 || 20 || >=22} + braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} @@ -4563,6 +4585,9 @@ packages: caniuse-lite@1.0.30001761: resolution: {integrity: sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==} + caniuse-lite@1.0.30001774: + resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} + chai@6.2.1: resolution: {integrity: sha512-p4Z49OGG5W/WBCPSS/dH3jQ73kD6tiMmUM+bckNK6Jr5JHMG3k9bg/BvKR8lKmtVBKmOiuVaV2ws8s9oSbwysg==} engines: {node: '>=18'} @@ -4612,6 +4637,9 @@ packages: citty@0.1.6: resolution: {integrity: sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==} + citty@0.2.1: + resolution: {integrity: sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==} + cli-cursor@3.1.0: resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} engines: {node: '>=8'} @@ -4716,6 +4744,9 @@ packages: confbox@0.2.2: resolution: {integrity: sha512-1NB+BKqhtNipMsov4xI/NnhCKp9XG9NamYp5PVm9klAT0fsrNPjaFICsCFhNhwZJKNh7zB/3q8qXz0E9oaMNtQ==} + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + consola@3.4.2: resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==} engines: {node: ^14.18.0 || >=16.10.0} @@ -4741,10 +4772,6 @@ packages: resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} engines: {node: '>=6.6.0'} - cookie@0.6.0: - resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} - engines: {node: '>= 0.6'} - cookie@0.7.1: resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} engines: {node: '>= 0.6'} @@ -5004,20 +5031,14 @@ packages: resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} engines: {node: '>=8'} - devalue@5.4.2: - resolution: {integrity: sha512-MwPZTKEPK2k8Qgfmqrd48ZKVvzSQjgW0lXLxiIBA8dQjtf/6mw6pggHNLcyDKyf+fI6eXxlQwPsfaCMTU5U+Bw==} - - devalue@5.6.1: - resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} - - devalue@5.6.2: - resolution: {integrity: sha512-nPRkjWzzDQlsejL1WVifk5rvcFi/y1onBRxjaFMjZeR9mFpqu2gmAZ9xUB9/IEanEP/vBtGeGganC/GO1fmufg==} + devalue@5.6.3: + resolution: {integrity: sha512-nc7XjUU/2Lb+SvEFVGcWLiKkzfw8+qHI7zn8WYXKkLMgfGSHbgCEaR6bJpev8Cm6Rmrb19Gfd/tZvGqx9is3wg==} dezalgo@1.0.4: resolution: {integrity: sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==} - diff@8.0.2: - resolution: {integrity: sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==} + diff@8.0.3: + resolution: {integrity: sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==} engines: {node: '>=0.3.1'} doctrine@2.1.0: @@ -5211,6 +5232,10 @@ packages: resolution: {integrity: sha512-FDWG5cmEYf2Z00IkYRhbFrwIwvdFKH07uV8dvNy0omp/Qb1xcyCWp2UDtcwJF4QZZvk0sLudP6/hAu42TaqVhQ==} engines: {node: '>=0.12'} + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -5239,6 +5264,9 @@ packages: es-module-lexer@1.7.0: resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + es-object-atoms@1.1.1: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} @@ -5255,11 +5283,6 @@ packages: resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} engines: {node: '>= 0.4'} - esbuild@0.25.11: - resolution: {integrity: sha512-KohQwyzrKTQmhXDW1PjCv3Tyspn9n5GcY2RTDqeORIdIJY8yKIF7sTSopFmn/wpMPW4rdPXI0UE5LJLuq3bx0Q==} - engines: {node: '>=18'} - hasBin: true - esbuild@0.25.5: resolution: {integrity: sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==} engines: {node: '>=18'} @@ -5270,6 +5293,11 @@ packages: engines: {node: '>=18'} hasBin: true + esbuild@0.27.3: + resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==} + engines: {node: '>=18'} + hasBin: true + escalade@3.2.0: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} @@ -5396,9 +5424,6 @@ packages: resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} engines: {node: '>=0.10'} - esrap@2.2.1: - resolution: {integrity: sha512-GiYWG34AN/4CUyaWAgunGt0Rxvr1PTMlGC0vvEov/uOQYWne2bpN03Um+k8jT+q3op33mKouP2zeJ6OlM+qeUg==} - esrap@2.2.3: resolution: {integrity: sha512-8fOS+GIGCQZl/ZIlhl59htOlms6U8NvX6ZYgYHpRU/b6tVSh3uHkOHZikl3D4cMbYM0JlpBe+p/BkZEi8J9XIQ==} @@ -5514,8 +5539,8 @@ packages: fastify-plugin@5.1.0: resolution: {integrity: sha512-FAIDA8eovSt5qcDgcBvDuX/v0Cjz0ohGhENZ/wpc3y+oZCY2afZ9Baqql3g/lC+OHRnciQol4ww7tuthOb9idw==} - fastify@5.6.1: - resolution: {integrity: sha512-WjjlOciBF0K8pDUPZoGPhqhKrQJ02I8DKaDIfO51EL0kbSMwQFl85cRwhOvmSDWoukNOdTo27gLN549pLCcH7Q==} + fastify@5.7.4: + resolution: {integrity: sha512-e6l5NsRdaEP8rdD8VR0ErJASeyaRbzXYpmkrpr2SuvuMq6Si3lvsaVy5C+7gLanEkvjpMDzBXWE5HPeb/hgTxA==} fastq@1.19.1: resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==} @@ -5634,6 +5659,9 @@ packages: resolution: {integrity: sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==} engines: {node: '>=10'} + fzf@0.5.2: + resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==} + generate-function@2.3.1: resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==} @@ -5679,11 +5707,9 @@ packages: resolution: {integrity: sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==} hasBin: true - git-up@8.1.1: - resolution: {integrity: sha512-FDenSF3fVqBYSaJoYy1KSc2wosx0gCvKP+c+PRBht7cAaiCeQlBtfBDX9vgnNOHmdePlSFITVcn4pFfcgNvx3g==} - - git-url-parse@16.1.0: - resolution: {integrity: sha512-cPLz4HuK86wClEW7iDdeAKcCVlWXmrLpb2L+G9goW0Z1dtpNS6BXXSOckUTlJT/LDQViE1QZKstNORzHsLnobw==} + giget@3.1.2: + resolution: {integrity: sha512-T2qUpKBHeUTwHcIhydgnJzhL0Hj785ms+JkxaaWQH9SDM/llXeewnOkfJcFShAHjWI+26hOChwUfCoupaXLm8g==} + hasBin: true github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -5696,8 +5722,8 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true @@ -5707,6 +5733,10 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + global-directory@4.0.1: resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==} engines: {node: '>=18'} @@ -5723,8 +5753,8 @@ packages: resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} engines: {node: '>= 0.4'} - globby@15.0.0: - resolution: {integrity: sha512-oB4vkQGqlMl682wL1IlWd02tXCbquGWM4voPEI85QmNKCaw8zGTm1f1rubFgkg3Eli2PtKlFgrnmUqasbQWlkw==} + globby@16.1.1: + resolution: {integrity: sha512-dW7vl+yiAJSp6aCekaVnVJxurRv7DCOLyXqEG3RYMYUg7AuJ2jCqPkZTA8ooqC2vtnkaMcV5WfFBMuEnTu1OQg==} engines: {node: '>=20'} gopd@1.2.0: @@ -5741,8 +5771,8 @@ packages: resolution: {integrity: sha512-O1Ld7Dr+nqPnmGpdhzLmMTQ4vAsD+rHwMm1NLUmoUFFymBOMKxCCrtDxqdBRYXdeEPEi3SyoR4TizJLQrnKBNA==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - h3@1.15.4: - resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} + h3@1.15.5: + resolution: {integrity: sha512-xEyq3rSl+dhGX2Lm0+eFQIAzlDN6Fs0EcC4f7BNUmzaRX/PTzeuM+Tr2lHB8FoXggsQIeXLj8EDVgs5ywxyxmg==} happy-dom@20.0.10: resolution: {integrity: sha512-6umCCHcjQrhP5oXhrHQQvLB0bwb1UzHAHdsXy+FjtKoYjUhmNZsQL8NivwM1vDvNEChJabVrUYxUnp/ZdYmy2g==} @@ -5785,13 +5815,16 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hono@4.10.3: - resolution: {integrity: sha512-2LOYWUbnhdxdL8MNbNg9XZig6k+cZXm5IjHn2Aviv7honhBMOHb+jxrKIeJRZJRmn+htUCKhaicxwXuUDlchRA==} + hono@4.12.2: + resolution: {integrity: sha512-gJnaDHXKDayjt8ue0n8Gs0A007yKXj4Xzb8+cNjZeYsSzzwKc0Lr+OZgYwVfB0pHfUs17EPoLvrOsEaJ9mj+Tg==} engines: {node: '>=16.9.0'} hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + hookable@6.0.1: + resolution: {integrity: sha512-uKGyY8BuzN/a5gvzvA+3FVWo0+wUjgtfSdnmjtrOVwQCZPHpHDH2WRO3VZSOeluYrHoDCiXFffZXs8Dj1ULWtw==} + hosted-git-info@2.8.9: resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} @@ -5884,8 +5917,8 @@ packages: resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} engines: {node: '>= 0.4'} - ioredis@5.8.2: - resolution: {integrity: sha512-C6uC+kleiIMmjViJINWk80sOQw5lEzse1ZmvD+S/s8p8CWapftSaC+kocGTx6xrbrJ4WmYQGC08ffHLr6ToR6Q==} + ioredis@5.9.3: + resolution: {integrity: sha512-VI5tMCdeoxZWU5vjHWsiE/Su76JGhBvWF1MJnV9ZtGltHk9BmD48oDq8Tj8haZ85aceXZMxLNDQZRVo5QKNgXA==} engines: {node: '>=12.22.0'} ipaddr.js@1.9.1: @@ -6037,9 +6070,6 @@ packages: resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} engines: {node: '>= 0.4'} - is-ssh@1.4.1: - resolution: {integrity: sha512-JNeu1wQsHjyHgn9NcWTaXq6zWSR6hqE0++zhfZlkFBbScNkyvxCdeV8sRkSBaeLKxmbpR21brail63ACNxJ0Tg==} - is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -6157,8 +6187,8 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} - js-yaml@4.1.0: - resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true jsdom@27.1.0: @@ -6387,8 +6417,8 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash-es@4.17.21: - resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==} + lodash-es@4.17.23: + resolution: {integrity: sha512-kVI48u3PZr38HdYz98UmfPnXl2DXrpdctLrFLCd3kOx1xUkOmpFPx7gCWWM5MPkL/fD8zb+Ph0QzjGFs4+hHWg==} lodash.defaults@4.2.0: resolution: {integrity: sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==} @@ -6408,8 +6438,8 @@ packages: lodash.uniq@4.5.0: resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - lodash@4.17.21: - resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} @@ -6533,11 +6563,6 @@ packages: engines: {node: '>=4.0.0'} hasBin: true - mime@3.0.0: - resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} - engines: {node: '>=10.0.0'} - hasBin: true - mime@4.1.0: resolution: {integrity: sha512-X5ju04+cAzsojXKes0B/S4tcYtFAJ6tTMuSPBEn9CPGlrWr8Fiw7qYeLT0XyH80HSoAoqWCaz+MWKh22P7G1cw==} engines: {node: '>=16'} @@ -6555,19 +6580,19 @@ packages: resolution: {integrity: sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==} engines: {node: '>=10'} - minimatch@10.1.1: - resolution: {integrity: sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==} - engines: {node: 20 || >=22} + minimatch@10.2.4: + resolution: {integrity: sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==} + engines: {node: 18 || 20 || >=22} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@5.1.6: - resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==} + minimatch@5.1.9: + resolution: {integrity: sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==} engines: {node: '>=10'} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.8: + resolution: {integrity: sha512-reYkDYtj/b19TeqbNZCV4q9t+Yxylf/rYBsLb42SXJatTv4/ylq5lEiAmhA/IToxO7NI2UzNMghHoHuaqDkAjw==} engines: {node: '>=16 || 14 >=14.17'} minimist@1.2.8: @@ -6577,6 +6602,10 @@ packages: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} engines: {node: '>=16 || 14 >=14.17'} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + minizlib@3.1.0: resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==} engines: {node: '>= 18'} @@ -6662,8 +6691,8 @@ packages: resolution: {integrity: sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==} engines: {node: '>= 0.6'} - next@16.0.10: - resolution: {integrity: sha512-RtWh5PUgI+vxlV3HdR+IfWA1UUHu0+Ram/JBO4vWB54cVPentCD0e+lxyAYEsDTqGGMg7qpjhKh6dc6aW7W/sA==} + next@16.1.6: + resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==} engines: {node: '>=20.9.0'} hasBin: true peerDependencies: @@ -6686,8 +6715,8 @@ packages: nice-try@1.0.5: resolution: {integrity: sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==} - nitropack@2.12.9: - resolution: {integrity: sha512-t6qqNBn2UDGMWogQuORjbL2UPevB8PvIPsPHmqvWpeGOlPr4P8Oc5oA8t3wFwGmaolM2M/s2SwT23nx9yARmOg==} + nitropack@2.13.1: + resolution: {integrity: sha512-2dDj89C4wC2uzG7guF3CnyG+zwkZosPEp7FFBGHB3AJo11AywOolWhyQJFHDzve8COvGxJaqscye9wW2IrUsNw==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6719,16 +6748,16 @@ packages: encoding: optional: true - node-forge@1.3.1: - resolution: {integrity: sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA==} + node-forge@1.3.3: + resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} engines: {node: '>= 6.13.0'} node-gyp-build@4.8.4: resolution: {integrity: sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==} hasBin: true - node-mock-http@1.0.3: - resolution: {integrity: sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==} + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} node-releases@2.0.27: resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} @@ -6761,8 +6790,8 @@ packages: nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - nuxt@4.2.2: - resolution: {integrity: sha512-n6oYFikgLEb70J4+K19jAzfx4exZcRSRX7yZn09P5qlf2Z59VNOBqNmaZO5ObzvyGUZ308SZfL629/Q2v2FVjw==} + nuxt@4.3.1: + resolution: {integrity: sha512-bl+0rFcT5Ax16aiWFBFPyWcsTob19NTZaDL5P6t0MQdK63AtgS6fN6fwvwdbXtnTk6/YdCzlmuLzXhSM22h0OA==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -6779,6 +6808,11 @@ packages: engines: {node: ^14.16.0 || >=16.10.0} hasBin: true + nypm@0.6.5: + resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==} + engines: {node: '>=18'} + hasBin: true + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -6820,11 +6854,14 @@ packages: ofetch@1.5.1: resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + ofetch@2.0.0-alpha.3: + resolution: {integrity: sha512-zpYTCs2byOuft65vI3z43Dd6iSdFbOZZLb9/d21aCpx2rGastVU9dOCv0lu4ykc1Ur1anAYjDi3SUvR0vq50JA==} + ohash@2.0.11: resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} - on-change@6.0.1: - resolution: {integrity: sha512-P7o0hkMahOhjb1niG28vLNAXsJrRcfpJvYWcTmPt/Tf4xedcF2PA1E9++N1tufY8/vIsaiJgHhjQp53hJCe+zw==} + on-change@6.0.2: + resolution: {integrity: sha512-08+12qcOVEA0fS9g/VxKS27HaT94nRutUT77J2dr8zv/unzXopvhBuF8tNLWsoLQ5IgrQ6eptGeGqUYat82U1w==} engines: {node: '>=20'} on-exit-leak-free@2.1.2: @@ -6872,20 +6909,20 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} - oxc-minify@0.102.0: - resolution: {integrity: sha512-FphAHDyTCNepQbiQTSyWFMbNc9zdUmj1WBsoLwvZhWm7rEe/IeIKYKRhy75lWOjwFsi5/i4Qucq43hgs3n2Exw==} + oxc-minify@0.112.0: + resolution: {integrity: sha512-rkVSeeIRSt+RYI9uX6xonBpLUpvZyegxIg0UL87ev7YAfUqp7IIZlRjkgQN5Us1lyXD//TOo0Dcuuro/TYOWoQ==} engines: {node: ^20.19.0 || >=22.12.0} - oxc-parser@0.102.0: - resolution: {integrity: sha512-xMiyHgr2FZsphQ12ZCsXRvSYzmKXCm1ejmyG4GDZIiKOmhyt5iKtWq0klOfFsEQ6jcgbwrUdwcCVYzr1F+h5og==} + oxc-parser@0.112.0: + resolution: {integrity: sha512-7rQ3QdJwobMQLMZwQaPuPYMEF2fDRZwf51lZ//V+bA37nejjKW5ifMHbbCwvA889Y4RLhT+/wLJpPRhAoBaZYw==} engines: {node: ^20.19.0 || >=22.12.0} - oxc-transform@0.102.0: - resolution: {integrity: sha512-MR5ohiBS6/kvxRpmUZ3LIDTTJBEC4xLAEZXfYr7vrA0eP7WHewQaNQPFDgT4Bee89TdmVQ5ZKrifGwxLjSyHHw==} + oxc-transform@0.112.0: + resolution: {integrity: sha512-cIRRvZgrHfsAHrkt8LWdAX4+Do8R0MzQSfeo9yzErzHeYiuyNiP4PCTPbOy/wBXL4MYzt3ebrBa5jt3akQkKAg==} engines: {node: ^20.19.0 || >=22.12.0} - oxc-walker@0.6.0: - resolution: {integrity: sha512-BA3hlxq5+Sgzp7TCQF52XDXCK5mwoIZuIuxv/+JuuTzOs2RXkLqWZgZ69d8pJDDjnL7wiREZTWHBzFp/UWH88Q==} + oxc-walker@0.7.0: + resolution: {integrity: sha512-54B4KUhrzbzc4sKvKwVYm7E2PgeROpGba0/2nlNZMqfDyca+yOor5IMb4WLGBatGDT0nkzYdYuzylg7n3YfB7A==} peerDependencies: oxc-parser: '>=0.98.0' @@ -6915,13 +6952,6 @@ packages: resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} engines: {node: '>=18'} - parse-path@7.1.0: - resolution: {integrity: sha512-EuCycjZtfPcjWk7KTksnJ5xPMvWGA/6i4zrLYhRG0hGvC3GPU/jGUj3Cy+ZR0v30duV3e23R95T1lE2+lsndSw==} - - parse-url@9.2.0: - resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} - engines: {node: '>=14.13.0'} - parse5@8.0.0: resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==} @@ -6959,6 +6989,10 @@ packages: resolution: {integrity: sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==} engines: {node: 20 || >=22} + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + path-to-regexp@8.3.0: resolution: {integrity: sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==} @@ -6966,10 +7000,6 @@ packages: resolution: {integrity: sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==} engines: {node: '>=4'} - path-type@6.0.0: - resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} - engines: {node: '>=18'} - pathe@1.1.2: resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} @@ -6982,6 +7012,9 @@ packages: perfect-debounce@2.0.0: resolution: {integrity: sha512-fkEH/OBiKrqqI/yIgjR92lMfs2K8105zt/VT6+7eTjNwisrsh47CeIED9z58zI7DfKdH3uHAn25ziRZn3kgAow==} + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + pg-cloudflare@1.2.7: resolution: {integrity: sha512-YgCtzMH0ptvZJslLM1ffsY4EuGaU0cx4XSdXLRFae8bPP4dS5xL1tNB3k2o/N64cHJpwU7dxKli/nZ2lUa5fLg==} @@ -7048,14 +7081,14 @@ packages: resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} engines: {node: '>=4'} - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} + pino-abstract-transport@3.0.0: + resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} pino-std-serializers@7.0.0: resolution: {integrity: sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==} - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} + pino@10.3.1: + resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} hasBin: true pirates@4.0.7: @@ -7426,9 +7459,6 @@ packages: resolution: {integrity: sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==} engines: {node: '>= 8'} - protocols@2.0.2: - resolution: {integrity: sha512-hHVTzba3wboROl0/aWRRG9dMytgH6ow//STBZh43l/wQgmMhYhOFi0EHWAPtoCz9IAUymsyP0TSBHkhgMEGNnQ==} - proxy-addr@2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -7443,12 +7473,8 @@ packages: pure-rand@6.1.0: resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.13.0: - resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} - engines: {node: '>=0.6'} - - qs@6.14.0: - resolution: {integrity: sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==} + qs@6.15.0: + resolution: {integrity: sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==} engines: {node: '>=0.6'} quansync@0.2.11: @@ -7480,6 +7506,9 @@ packages: rc9@2.1.2: resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} + rc9@3.0.0: + resolution: {integrity: sha512-MGOue0VqscKWQ104udASX/3GYDcKyPI4j4F8gu/jHHzglpmy9a/anZK3PNe8ug6aZFl+9GxLtdhe3kVZuMaQbA==} + rc@1.2.8: resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} hasBin: true @@ -7611,13 +7640,8 @@ packages: rollup: optional: true - rollup@4.44.0: - resolution: {integrity: sha512-qHcdEzLCiktQIfwBq420pn2dP+30uzqYxv9ETm91wdt2R9AFcWfjNAmje4NWlnCIQ5RMTzVf0ZyisOKqHR6RwA==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - rollup@4.52.5: - resolution: {integrity: sha512-3GuObel8h7Kqdjt0gxkEzaifHTqLVW56Y/bjN7PSQtkKr0w3V/QYSdt6QWYtd7A1xUtYQigtdUfgj1RvWVtorw==} + rollup@4.59.0: + resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -7701,6 +7725,11 @@ packages: engines: {node: '>=10'} hasBin: true + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + send@1.2.0: resolution: {integrity: sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==} engines: {node: '>= 18'} @@ -7711,8 +7740,8 @@ packages: serialize-javascript@6.0.2: resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} - seroval@1.4.1: - resolution: {integrity: sha512-9GOc+8T6LN4aByLN75uRvMbrwY5RDBW6lSlknsY4LEa9ZmWcxKcRe1G/Q3HZXjltxMHTrStnvrwAICxZrhldtg==} + seroval@1.5.0: + resolution: {integrity: sha512-OE4cvmJ1uSPrKorFIH9/w/Qwuvi/IMcGbv5RKgcJ/zjA/IohDLU6SVaxFN9FwajbP7nsX0dQqMDes1whk3y+yw==} engines: {node: '>=10'} serve-placeholder@2.0.2: @@ -7722,9 +7751,16 @@ packages: resolution: {integrity: sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==} engines: {node: '>= 18'} + serve-static@2.2.1: + resolution: {integrity: sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==} + engines: {node: '>= 18'} + set-cookie-parser@2.7.2: resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + set-cookie-parser@3.0.1: + resolution: {integrity: sha512-n7Z7dXZhJbwuAHhNzkTti6Aw9QDDjZtm3JTpTGATIdNzdQz5GuFs22w90BcvF4INfnrL5xrX3oGsuqO5Dx3A1Q==} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -7863,8 +7899,8 @@ packages: resolution: {integrity: sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==} engines: {node: '>= 0.6'} - srvx@0.9.8: - resolution: {integrity: sha512-RZaxTKJEE/14HYn8COLuUOJAt0U55N9l1Xf6jj+T0GoA01EUH1Xz5JtSUOI+EHn+AEgPCVn7gk6jHJffrr06fQ==} + srvx@0.11.8: + resolution: {integrity: sha512-2n9t0YnAXPJjinytvxccNgs7rOA5gmE7Wowt/8Dy2dx2fDC6sBhfBpbrCvjYKALlVukPS/Uq3QwkolKNa7P/2Q==} engines: {node: '>=20.16.0'} hasBin: true @@ -8039,12 +8075,8 @@ packages: svelte: ^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0 typescript: ^4.9.4 || ^5.0.0 - svelte@5.45.6: - resolution: {integrity: sha512-V3aVXthzPyPt1UB1wLEoXnEXpwPsvs7NHrR0xkCor8c11v71VqBj477MClqPZYyrcXrAH21sNGhOj9FJvSwXfQ==} - engines: {node: '>=18'} - - svelte@5.46.1: - resolution: {integrity: sha512-ynjfCHD3nP2el70kN5Pmg37sSi0EjOm9FgHYQdC4giWG/hzO3AatzXXJJgP305uIhGQxSufJLuYWtkY8uK/8RA==} + svelte@5.53.5: + resolution: {integrity: sha512-YkqERnF05g8KLdDZwZrF8/i1eSbj6Eoat8Jjr2IfruZz9StLuBqo8sfCSzjosNKd+ZrQ8DkKZDjpO5y3ht1Pow==} engines: {node: '>=18'} svgo@4.0.0: @@ -8073,8 +8105,8 @@ packages: resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} engines: {node: '>=6'} - tar-fs@2.1.2: - resolution: {integrity: sha512-EsaAXwxmx8UB7FRKqeozqEPop69DXcmYwTQwXvyAPF352HJsPdkVhvTaDPYqfNgruveJIJy3TA2l+2zj8LJIJA==} + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} @@ -8083,10 +8115,9 @@ packages: tar-stream@3.1.7: resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==} - tar@7.5.1: - resolution: {integrity: sha512-nlGpxf+hv0v7GkWBK2V9spgactGOp0qvfWRxUMjqHyzrt3SgwE48DIv/FhqPHJYLHpgW1opq3nERbz5Anq7n1g==} + tar@7.5.9: + resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} engines: {node: '>=18'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me terser@5.44.0: resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} @@ -8103,8 +8134,9 @@ packages: thenify@3.3.1: resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} + thread-stream@4.0.0: + resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==} + engines: {node: '>=20'} tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -8141,8 +8173,8 @@ packages: resolution: {integrity: sha512-ASdhgQIBSay0R/eXggAkQ53G4nTJqTXqC2kbaBbdDwM7SkjyZyO0OaaN1/FH7U/yCeqOHDwFO5j8+Os/IS1dXw==} hasBin: true - tmp@0.2.3: - resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} + tmp@0.2.5: + resolution: {integrity: sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==} engines: {node: '>=14.14'} to-regex-range@5.0.1: @@ -8322,6 +8354,9 @@ packages: ufo@1.6.1: resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + uint8array-extras@1.5.0: resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==} engines: {node: '>=18'} @@ -8340,8 +8375,8 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} - unctx@2.4.1: - resolution: {integrity: sha512-AbaYw0Nm4mK4qjhns67C+kgxR2YWiwlDBPzxrN8h8C6VtAdCgditAY5Dezu3IJy4XVqAnbrXt9oQJvsn3fyozg==} + unctx@2.5.0: + resolution: {integrity: sha512-p+Rz9x0R7X+CYDkT+Xg8/GhpcShTlU8n+cf9OtOEf7zEQsNcCZO1dPKNRDqvUTaq+P32PMMkxWHwfrxkqfqAYg==} undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -8352,15 +8387,19 @@ packages: unenv@2.0.0-rc.24: resolution: {integrity: sha512-i7qRCmY42zmCwnYlh9H2SvLEypEFGye5iRmEMKjcGi7zk9UquigRjFtTLz0TYqr0ZGLZhaMHl/foy1bZR+Cwlw==} - unhead@2.0.19: - resolution: {integrity: sha512-gEEjkV11Aj+rBnY6wnRfsFtF2RxKOLaPN4i+Gx3UhBxnszvV6ApSNZbGk7WKyy/lErQ6ekPN63qdFL7sa1leow==} + unhead@2.1.7: + resolution: {integrity: sha512-LZlyHgDnDieyhpBnkd80pn/kVum0P15nNs4DtPUvRwd98uuS1Xqmlc2Ms48P88HI5nLo3nkqHWg2VOs/RgcOWg==} unicorn-magic@0.3.0: resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} engines: {node: '>=18'} - unimport@5.5.0: - resolution: {integrity: sha512-/JpWMG9s1nBSlXJAQ8EREFTFy3oy6USFd8T6AoBaw1q2GGcF4R9yp3ofg32UODZlYEO5VD0EWE1RpI9XDWyPYg==} + unicorn-magic@0.4.0: + resolution: {integrity: sha512-wH590V9VNgYH9g3lH9wWjTrUoKsjLF6sGLjhR4sH1LWpLmCOH0Zf7PukhDA8BiS7KHe4oPNkcTHqYkj7SOGUOw==} + engines: {node: '>=20'} + + unimport@5.6.0: + resolution: {integrity: sha512-8rqAmtJV8o60x46kBAJKtHpJDJWkA2xcBqWKPI14MgUb05o1pnpnCnXSxedUXyeq7p8fR5g3pTo2BaswZ9lD9A==} engines: {node: '>=18.12.0'} universalify@2.0.1: @@ -8379,8 +8418,9 @@ packages: resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} engines: {node: '>=20.19.0'} - unplugin-vue-router@0.19.1: - resolution: {integrity: sha512-LJVRzfxS4j34K4sx4pggzhqpfAtXNZ6mLLRHvlSbDw11lWKLluuLXRbSWLXfiVj4RHeNHXu/+XxsGX65Ogu07Q==} + unplugin-vue-router@0.19.2: + resolution: {integrity: sha512-u5dgLBarxE5cyDK/hzJGfpCTLIAyiTXGlo85COuD4Nssj6G7NxS+i9mhCWz/1p/ud1eMwdcUbTXehQe41jYZUA==} + deprecated: 'Merged into vuejs/router. Migrate: https://router.vuejs.org/guide/migration/v4-to-v5.html' peerDependencies: '@vue/compiler-sfc': ^3.5.17 vue-router: ^4.6.0 @@ -8392,11 +8432,15 @@ packages: resolution: {integrity: sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==} engines: {node: '>=18.12.0'} + unplugin@3.0.0: + resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} + engines: {node: ^20.19.0 || >=22.12.0} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - unstorage@1.17.3: - resolution: {integrity: sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==} + unstorage@1.17.4: + resolution: {integrity: sha512-fHK0yNg38tBiJKp/Vgsq4j0JEsCmgqH58HAn707S7zGkArbZsVr/CwINoi+nh3h98BRCwKvx1K3Xg9u3VV83sw==} peerDependencies: '@azure/app-configuration': ^1.8.0 '@azure/cosmos': ^4.2.0 @@ -8404,14 +8448,14 @@ packages: '@azure/identity': ^4.6.0 '@azure/keyvault-secrets': ^4.9.0 '@azure/storage-blob': ^12.26.0 - '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@capacitor/preferences': ^6 || ^7 || ^8 '@deno/kv': '>=0.9.0' '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 '@planetscale/database': ^1.19.0 '@upstash/redis': ^1.34.3 '@vercel/blob': '>=0.27.1' '@vercel/functions': ^2.2.12 || ^3.0.0 - '@vercel/kv': ^1.0.1 + '@vercel/kv': ^1 || ^2 || ^3 aws4fetch: ^1.0.20 db0: '>=0.2.1' idb-keyval: ^6.2.1 @@ -8465,8 +8509,8 @@ packages: resolution: {integrity: sha512-nwNCjxJTjNuLCgFr42fEak5OcLuB3ecca+9ksPFNvtfYSLpjf+iJqSIaSnIile6ZPbKYxI5k2AfXqeopGudK/g==} hasBin: true - unwasm@0.3.11: - resolution: {integrity: sha512-Vhp5gb1tusSQw5of/g3Q697srYgMXvwMgXMjcG4ZNga02fDX9coxJ9fAb0Ci38hM2Hv/U1FXRPGgjP2BYqhNoQ==} + unwasm@0.5.3: + resolution: {integrity: sha512-keBgTSfp3r6+s9ZcSma+0chwxQdmLbB5+dAD9vjtB21UTMYuKAxHXCU1K2CbCtnP09EaWeRvACnXk0EJtUx+hw==} update-browserslist-db@1.1.4: resolution: {integrity: sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==} @@ -8514,8 +8558,8 @@ packages: peerDependencies: vite: ^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0 || ^7.0.0-0 - vite-node@5.2.0: - resolution: {integrity: sha512-7UT39YxUukIA97zWPXUGb0SGSiLexEGlavMwU3HDE6+d/HJhKLjLqu4eX2qv6SQiocdhKLRcusroDwXHQ6CnRQ==} + vite-node@5.3.0: + resolution: {integrity: sha512-8f20COPYJujc3OKPX6OuyBy3ZIv2det4eRRU4GY1y2MjbeGSUmPjedxg1b72KnTagCofwvZ65ThzjxDW2AtQFQ==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true @@ -8612,6 +8656,46 @@ packages: yaml: optional: true + vite@7.3.1: + resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + vitefu@1.1.1: resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} peerDependencies: @@ -8717,8 +8801,8 @@ packages: typescript: optional: true - vue@3.5.26: - resolution: {integrity: sha512-SJ/NTccVyAoNUJmkM9KUqPcYlY+u8OVL1X5EW9RIs3ch5H2uERxyyIUI4MRxVCSOiEcupX9xNGde1tL9ZKpimA==} + vue@3.5.29: + resolution: {integrity: sha512-BZqN4Ze6mDQVNAni0IHeMJ5mwr8VAJ3MQC9FmprRhcBYENw+wOAAjRj8jfmN6FLl0j96OXbR+CjWhmAmM+QGnA==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -8822,18 +8906,6 @@ packages: wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - ws@8.18.3: - resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==} - engines: {node: '>=10.0.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: '>=5.0.2' - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - ws@8.19.0: resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} engines: {node: '>=10.0.0'} @@ -8967,8 +9039,16 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + '@babel/compat-data@7.28.5': {} + '@babel/compat-data@7.29.0': {} + '@babel/core@7.28.5': dependencies: '@babel/code-frame': 7.27.1 @@ -8989,6 +9069,26 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/generator@7.28.5': dependencies: '@babel/parser': 7.28.5 @@ -9005,6 +9105,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + '@babel/helper-annotate-as-pure@7.27.3': dependencies: '@babel/types': 7.28.5 @@ -9017,15 +9125,23 @@ snapshots: lru-cache: 5.1.1 semver: 6.3.1 - '@babel/helper-create-class-features-plugin@7.28.5(@babel/core@7.28.5)': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/core': 7.28.5 + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.27.1(@babel/core@7.28.5) + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 semver: 6.3.1 transitivePeerDependencies: - supports-color @@ -9062,9 +9178,9 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.6(@babel/core@7.28.5)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-module-imports': 7.28.6 '@babel/helper-validator-identifier': 7.28.5 '@babel/traverse': 7.28.6 @@ -9075,16 +9191,14 @@ snapshots: dependencies: '@babel/types': 7.28.5 - '@babel/helper-plugin-utils@7.27.1': {} - '@babel/helper-plugin-utils@7.28.6': {} - '@babel/helper-replace-supers@7.27.1(@babel/core@7.28.5)': + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-member-expression-to-functions': 7.28.5 '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.28.5 + '@babel/traverse': 7.28.6 transitivePeerDependencies: - supports-color @@ -9106,6 +9220,11 @@ snapshots: '@babel/template': 7.27.2 '@babel/types': 7.28.5 + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@babel/parser@7.28.5': dependencies: '@babel/types': 7.28.5 @@ -9117,91 +9236,85 @@ snapshots: '@babel/parser@7.29.0': dependencies: '@babel/types': 7.29.0 - optional: true - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.28.5)': - dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.28.5)': + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) '@babel/helper-plugin-utils': 7.28.6 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.28.5)': + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.5) + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.28.5)': + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-module-imports': 7.28.6 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.5) - '@babel/types': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.28.5)': + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-transform-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.5(@babel/core@7.28.5) - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/preset-react@7.28.5(@babel/core@7.28.5)': + '@babel/preset-react@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.28.5) - '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) transitivePeerDependencies: - supports-color - '@babel/preset-typescript@7.28.5(@babel/core@7.28.5)': + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 '@babel/helper-plugin-utils': 7.28.6 '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.28.5) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) transitivePeerDependencies: - supports-color @@ -9243,6 +9356,18 @@ snapshots: transitivePeerDependencies: - supports-color + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + '@babel/types@7.28.5': dependencies: '@babel/helper-string-parser': 7.27.1 @@ -9257,23 +9382,22 @@ snapshots: dependencies: '@babel/helper-string-parser': 7.27.1 '@babel/helper-validator-identifier': 7.28.5 - optional: true '@bcoe/v8-coverage@1.0.2': {} - '@better-auth/cli@1.4.17(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.1.2)(kysely@0.28.8)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.0.1)(next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.46.1)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + '@better-auth/cli@1.4.19(@better-fetch/fetch@1.1.21)(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(@types/better-sqlite3@7.6.13)(@types/sql.js@1.4.9)(better-call@1.1.8(zod@4.3.6))(bun-types@1.3.3)(jose@6.1.2)(kysely@0.28.8)(magicast@0.5.1)(mysql2@3.16.1)(nanostores@1.0.1)(next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(sql.js@1.13.0)(svelte@5.53.5)(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': dependencies: - '@babel/core': 7.28.5 - '@babel/preset-react': 7.28.5(@babel/core@7.28.5) - '@babel/preset-typescript': 7.28.5(@babel/core@7.28.5) - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) - '@better-auth/telemetry': 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)) + '@babel/core': 7.29.0 + '@babel/preset-react': 7.28.5(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) + '@better-auth/telemetry': 1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)) '@better-auth/utils': 0.3.0 '@clack/prompts': 0.11.0 '@mrleebo/prisma-ast': 0.13.1 '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) '@types/pg': 8.16.0 - better-auth: 1.4.17(16db68be254775c88fdefd75497fac00) + better-auth: 1.4.19(824ab937a8901dc918eb211f1d922b51) better-sqlite3: 12.5.0 c12: 3.3.3(magicast@0.5.1) chalk: 5.6.2 @@ -9284,7 +9408,7 @@ snapshots: pg: 8.16.3 prettier: 3.8.1 prompts: 2.4.2 - semver: 7.7.3 + semver: 7.7.4 yocto-spinner: 0.2.3 zod: 4.3.6 transitivePeerDependencies: @@ -9333,7 +9457,7 @@ snapshots: - vitest - vue - '@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)': + '@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)': dependencies: '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 @@ -9344,9 +9468,9 @@ snapshots: nanostores: 1.0.1 zod: 4.3.6 - '@better-auth/telemetry@1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1))': + '@better-auth/telemetry@1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1))': dependencies: - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) + '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 @@ -9354,10 +9478,10 @@ snapshots: '@better-fetch/fetch@1.1.21': {} - '@bomb.sh/tab@0.0.10(cac@6.7.14)(citty@0.1.6)': + '@bomb.sh/tab@0.0.12(cac@6.7.14)(citty@0.2.1)': optionalDependencies: cac: 6.7.14 - citty: 0.1.6 + citty: 0.2.1 '@borewit/text-codec@0.2.1': {} @@ -9365,23 +9489,23 @@ snapshots: dependencies: '@chevrotain/gast': 10.5.0 '@chevrotain/types': 10.5.0 - lodash: 4.17.21 + lodash: 4.17.23 '@chevrotain/cst-dts-gen@11.0.3': dependencies: '@chevrotain/gast': 11.0.3 '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 '@chevrotain/gast@10.5.0': dependencies: '@chevrotain/types': 10.5.0 - lodash: 4.17.21 + lodash: 4.17.23 '@chevrotain/gast@11.0.3': dependencies: '@chevrotain/types': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 '@chevrotain/regexp-to-ast@11.0.3': {} @@ -9398,7 +9522,7 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 - '@clack/core@1.0.0-alpha.7': + '@clack/core@1.0.1': dependencies: picocolors: 1.1.1 sisteransi: 1.0.5 @@ -9409,15 +9533,13 @@ snapshots: picocolors: 1.1.1 sisteransi: 1.0.5 - '@clack/prompts@1.0.0-alpha.8': + '@clack/prompts@1.0.1': dependencies: - '@clack/core': 1.0.0-alpha.7 + '@clack/core': 1.0.1 picocolors: 1.1.1 sisteransi: 1.0.5 - '@cloudflare/kv-asset-handler@0.4.0': - dependencies: - mime: 3.0.0 + '@cloudflare/kv-asset-handler@0.4.2': {} '@csstools/color-helpers@6.0.1': optional: true @@ -9447,11 +9569,11 @@ snapshots: '@csstools/css-tokenizer@4.0.0': optional: true - '@dxup/nuxt@0.2.2(magicast@0.5.1)': + '@dxup/nuxt@0.3.2(magicast@0.5.1)': dependencies: '@dxup/unimport': 0.1.2 - '@nuxt/kit': 4.2.2(magicast@0.5.1) - chokidar: 4.0.3 + '@nuxt/kit': 4.3.1(magicast@0.5.1) + chokidar: 5.0.0 pathe: 2.0.3 tinyglobby: 0.2.15 transitivePeerDependencies: @@ -9487,16 +9609,13 @@ snapshots: tslib: 2.8.1 optional: true - '@esbuild/aix-ppc64@0.25.11': - optional: true - '@esbuild/aix-ppc64@0.25.5': optional: true '@esbuild/aix-ppc64@0.27.2': optional: true - '@esbuild/android-arm64@0.25.11': + '@esbuild/aix-ppc64@0.27.3': optional: true '@esbuild/android-arm64@0.25.5': @@ -9505,7 +9624,7 @@ snapshots: '@esbuild/android-arm64@0.27.2': optional: true - '@esbuild/android-arm@0.25.11': + '@esbuild/android-arm64@0.27.3': optional: true '@esbuild/android-arm@0.25.5': @@ -9514,7 +9633,7 @@ snapshots: '@esbuild/android-arm@0.27.2': optional: true - '@esbuild/android-x64@0.25.11': + '@esbuild/android-arm@0.27.3': optional: true '@esbuild/android-x64@0.25.5': @@ -9523,7 +9642,7 @@ snapshots: '@esbuild/android-x64@0.27.2': optional: true - '@esbuild/darwin-arm64@0.25.11': + '@esbuild/android-x64@0.27.3': optional: true '@esbuild/darwin-arm64@0.25.5': @@ -9532,7 +9651,7 @@ snapshots: '@esbuild/darwin-arm64@0.27.2': optional: true - '@esbuild/darwin-x64@0.25.11': + '@esbuild/darwin-arm64@0.27.3': optional: true '@esbuild/darwin-x64@0.25.5': @@ -9541,7 +9660,7 @@ snapshots: '@esbuild/darwin-x64@0.27.2': optional: true - '@esbuild/freebsd-arm64@0.25.11': + '@esbuild/darwin-x64@0.27.3': optional: true '@esbuild/freebsd-arm64@0.25.5': @@ -9550,7 +9669,7 @@ snapshots: '@esbuild/freebsd-arm64@0.27.2': optional: true - '@esbuild/freebsd-x64@0.25.11': + '@esbuild/freebsd-arm64@0.27.3': optional: true '@esbuild/freebsd-x64@0.25.5': @@ -9559,7 +9678,7 @@ snapshots: '@esbuild/freebsd-x64@0.27.2': optional: true - '@esbuild/linux-arm64@0.25.11': + '@esbuild/freebsd-x64@0.27.3': optional: true '@esbuild/linux-arm64@0.25.5': @@ -9568,7 +9687,7 @@ snapshots: '@esbuild/linux-arm64@0.27.2': optional: true - '@esbuild/linux-arm@0.25.11': + '@esbuild/linux-arm64@0.27.3': optional: true '@esbuild/linux-arm@0.25.5': @@ -9577,7 +9696,7 @@ snapshots: '@esbuild/linux-arm@0.27.2': optional: true - '@esbuild/linux-ia32@0.25.11': + '@esbuild/linux-arm@0.27.3': optional: true '@esbuild/linux-ia32@0.25.5': @@ -9586,7 +9705,7 @@ snapshots: '@esbuild/linux-ia32@0.27.2': optional: true - '@esbuild/linux-loong64@0.25.11': + '@esbuild/linux-ia32@0.27.3': optional: true '@esbuild/linux-loong64@0.25.5': @@ -9595,7 +9714,7 @@ snapshots: '@esbuild/linux-loong64@0.27.2': optional: true - '@esbuild/linux-mips64el@0.25.11': + '@esbuild/linux-loong64@0.27.3': optional: true '@esbuild/linux-mips64el@0.25.5': @@ -9604,7 +9723,7 @@ snapshots: '@esbuild/linux-mips64el@0.27.2': optional: true - '@esbuild/linux-ppc64@0.25.11': + '@esbuild/linux-mips64el@0.27.3': optional: true '@esbuild/linux-ppc64@0.25.5': @@ -9613,7 +9732,7 @@ snapshots: '@esbuild/linux-ppc64@0.27.2': optional: true - '@esbuild/linux-riscv64@0.25.11': + '@esbuild/linux-ppc64@0.27.3': optional: true '@esbuild/linux-riscv64@0.25.5': @@ -9622,7 +9741,7 @@ snapshots: '@esbuild/linux-riscv64@0.27.2': optional: true - '@esbuild/linux-s390x@0.25.11': + '@esbuild/linux-riscv64@0.27.3': optional: true '@esbuild/linux-s390x@0.25.5': @@ -9631,7 +9750,7 @@ snapshots: '@esbuild/linux-s390x@0.27.2': optional: true - '@esbuild/linux-x64@0.25.11': + '@esbuild/linux-s390x@0.27.3': optional: true '@esbuild/linux-x64@0.25.5': @@ -9640,7 +9759,7 @@ snapshots: '@esbuild/linux-x64@0.27.2': optional: true - '@esbuild/netbsd-arm64@0.25.11': + '@esbuild/linux-x64@0.27.3': optional: true '@esbuild/netbsd-arm64@0.25.5': @@ -9649,7 +9768,7 @@ snapshots: '@esbuild/netbsd-arm64@0.27.2': optional: true - '@esbuild/netbsd-x64@0.25.11': + '@esbuild/netbsd-arm64@0.27.3': optional: true '@esbuild/netbsd-x64@0.25.5': @@ -9658,7 +9777,7 @@ snapshots: '@esbuild/netbsd-x64@0.27.2': optional: true - '@esbuild/openbsd-arm64@0.25.11': + '@esbuild/netbsd-x64@0.27.3': optional: true '@esbuild/openbsd-arm64@0.25.5': @@ -9667,7 +9786,7 @@ snapshots: '@esbuild/openbsd-arm64@0.27.2': optional: true - '@esbuild/openbsd-x64@0.25.11': + '@esbuild/openbsd-arm64@0.27.3': optional: true '@esbuild/openbsd-x64@0.25.5': @@ -9676,13 +9795,13 @@ snapshots: '@esbuild/openbsd-x64@0.27.2': optional: true - '@esbuild/openharmony-arm64@0.25.11': + '@esbuild/openbsd-x64@0.27.3': optional: true '@esbuild/openharmony-arm64@0.27.2': optional: true - '@esbuild/sunos-x64@0.25.11': + '@esbuild/openharmony-arm64@0.27.3': optional: true '@esbuild/sunos-x64@0.25.5': @@ -9691,7 +9810,7 @@ snapshots: '@esbuild/sunos-x64@0.27.2': optional: true - '@esbuild/win32-arm64@0.25.11': + '@esbuild/sunos-x64@0.27.3': optional: true '@esbuild/win32-arm64@0.25.5': @@ -9700,7 +9819,7 @@ snapshots: '@esbuild/win32-arm64@0.27.2': optional: true - '@esbuild/win32-ia32@0.25.11': + '@esbuild/win32-arm64@0.27.3': optional: true '@esbuild/win32-ia32@0.25.5': @@ -9709,7 +9828,7 @@ snapshots: '@esbuild/win32-ia32@0.27.2': optional: true - '@esbuild/win32-x64@0.25.11': + '@esbuild/win32-ia32@0.27.3': optional: true '@esbuild/win32-x64@0.25.5': @@ -9718,6 +9837,9 @@ snapshots: '@esbuild/win32-x64@0.27.2': optional: true + '@esbuild/win32-x64@0.27.3': + optional: true + '@eslint-community/eslint-utils@4.7.0(eslint@9.29.0(jiti@2.6.1))': dependencies: eslint: 9.29.0(jiti@2.6.1) @@ -9729,7 +9851,7 @@ snapshots: dependencies: '@eslint/object-schema': 2.1.6 debug: 4.4.1 - minimatch: 3.1.2 + minimatch: 3.1.5 transitivePeerDependencies: - supports-color @@ -9739,20 +9861,20 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/core@0.15.0': + '@eslint/core@0.15.2': dependencies: '@types/json-schema': 7.0.15 '@eslint/eslintrc@3.3.1': dependencies: - ajv: 6.12.6 + ajv: 6.14.0 debug: 4.4.1 espree: 10.4.0 globals: 14.0.0 ignore: 5.3.2 import-fresh: 3.3.1 - js-yaml: 4.1.0 - minimatch: 3.1.2 + js-yaml: 4.1.1 + minimatch: 3.1.5 strip-json-comments: 3.1.1 transitivePeerDependencies: - supports-color @@ -9761,15 +9883,15 @@ snapshots: '@eslint/object-schema@2.1.6': {} - '@eslint/plugin-kit@0.3.2': + '@eslint/plugin-kit@0.3.5': dependencies: - '@eslint/core': 0.15.0 + '@eslint/core': 0.15.2 levn: 0.4.1 '@fastify/ajv-compiler@4.0.5': dependencies: - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) fast-uri: 3.1.0 '@fastify/error@4.2.0': {} @@ -9891,13 +10013,7 @@ snapshots: '@img/sharp-win32-x64@0.34.4': optional: true - '@ioredis/commands@1.4.0': {} - - '@isaacs/balanced-match@4.0.1': {} - - '@isaacs/brace-expansion@5.0.0': - dependencies: - '@isaacs/balanced-match': 4.0.1 + '@ioredis/commands@1.5.0': {} '@isaacs/cliui@8.0.2': dependencies: @@ -9920,7 +10036,7 @@ snapshots: '@jridgewell/gen-mapping@0.3.8': dependencies: '@jridgewell/set-array': 1.2.1 - '@jridgewell/sourcemap-codec': 1.5.0 + '@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/trace-mapping': 0.3.25 '@jridgewell/remapping@2.3.5': @@ -9966,8 +10082,8 @@ snapshots: https-proxy-agent: 7.0.6 node-fetch: 2.7.0 nopt: 8.1.0 - semver: 7.7.3 - tar: 7.5.1 + semver: 7.7.4 + tar: 7.5.9 transitivePeerDependencies: - encoding - supports-color @@ -9993,41 +10109,41 @@ snapshots: '@tybys/wasm-util': 0.10.1 optional: true - '@napi-rs/wasm-runtime@1.1.0': + '@napi-rs/wasm-runtime@1.1.1': dependencies: '@emnapi/core': 1.7.1 '@emnapi/runtime': 1.7.1 '@tybys/wasm-util': 0.10.1 optional: true - '@next/env@16.0.10': {} + '@next/env@16.1.6': {} '@next/eslint-plugin-next@16.0.1': dependencies: fast-glob: 3.3.1 - '@next/swc-darwin-arm64@16.0.10': + '@next/swc-darwin-arm64@16.1.6': optional: true - '@next/swc-darwin-x64@16.0.10': + '@next/swc-darwin-x64@16.1.6': optional: true - '@next/swc-linux-arm64-gnu@16.0.10': + '@next/swc-linux-arm64-gnu@16.1.6': optional: true - '@next/swc-linux-arm64-musl@16.0.10': + '@next/swc-linux-arm64-musl@16.1.6': optional: true - '@next/swc-linux-x64-gnu@16.0.10': + '@next/swc-linux-x64-gnu@16.1.6': optional: true - '@next/swc-linux-x64-musl@16.0.10': + '@next/swc-linux-x64-musl@16.1.6': optional: true - '@next/swc-win32-arm64-msvc@16.0.10': + '@next/swc-win32-arm64-msvc@16.1.6': optional: true - '@next/swc-win32-x64-msvc@16.0.10': + '@next/swc-win32-x64-msvc@16.1.6': optional: true '@noble/ciphers@2.0.1': {} @@ -10050,35 +10166,38 @@ snapshots: '@nolyfill/is-core-module@1.0.39': {} - '@nuxt/cli@3.31.3(cac@6.7.14)(magicast@0.5.1)': + '@nuxt/cli@3.33.1(@nuxt/schema@4.3.1)(cac@6.7.14)(magicast@0.5.1)': dependencies: - '@bomb.sh/tab': 0.0.10(cac@6.7.14)(citty@0.1.6) - '@clack/prompts': 1.0.0-alpha.8 + '@bomb.sh/tab': 0.0.12(cac@6.7.14)(citty@0.2.1) + '@clack/prompts': 1.0.1 c12: 3.3.3(magicast@0.5.1) - citty: 0.1.6 - confbox: 0.2.2 + citty: 0.2.1 + confbox: 0.2.4 consola: 3.4.2 copy-paste: 2.2.0 debug: 4.4.3 defu: 6.1.4 exsolve: 1.0.8 fuse.js: 7.1.0 - giget: 2.0.0 + fzf: 0.5.2 + giget: 3.1.2 jiti: 2.6.1 listhen: 1.9.0 - nypm: 0.6.2 + nypm: 0.6.5 ofetch: 1.5.1 ohash: 2.0.11 pathe: 2.0.3 - perfect-debounce: 2.0.0 + perfect-debounce: 2.1.0 pkg-types: 2.3.0 scule: 1.3.0 - semver: 7.7.3 - srvx: 0.9.8 + semver: 7.7.4 + srvx: 0.11.8 std-env: 3.10.0 tinyexec: 1.0.2 - ufo: 1.6.1 + ufo: 1.6.3 youch: 4.1.0-beta.13 + optionalDependencies: + '@nuxt/schema': 4.3.1 transitivePeerDependencies: - cac - commander @@ -10087,31 +10206,31 @@ snapshots: '@nuxt/devalue@2.0.2': {} - '@nuxt/devtools-kit@3.1.1(magicast@0.5.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@nuxt/devtools-kit@3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: - '@nuxt/kit': 4.2.2(magicast@0.5.1) + '@nuxt/kit': 4.3.1(magicast@0.5.1) execa: 8.0.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) transitivePeerDependencies: - magicast '@nuxt/devtools-wizard@3.1.1': dependencies: consola: 3.4.2 - diff: 8.0.2 + diff: 8.0.3 execa: 8.0.1 magicast: 0.5.1 pathe: 2.0.3 pkg-types: 2.3.0 prompts: 2.4.2 - semver: 7.7.3 + semver: 7.7.4 - '@nuxt/devtools@3.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + '@nuxt/devtools@3.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': dependencies: - '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@nuxt/devtools-kit': 3.1.1(magicast@0.5.1)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@nuxt/devtools-wizard': 3.1.1 - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@vue/devtools-core': 8.0.5(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + '@nuxt/kit': 4.3.1(magicast@0.5.1) + '@vue/devtools-core': 8.0.5(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) '@vue/devtools-kit': 8.0.5 birpc: 2.9.0 consola: 3.4.2 @@ -10126,54 +10245,28 @@ snapshots: launch-editor: 2.12.0 local-pkg: 1.1.2 magicast: 0.5.1 - nypm: 0.6.2 + nypm: 0.6.5 ohash: 2.0.11 pathe: 2.0.3 - perfect-debounce: 2.0.0 + perfect-debounce: 2.1.0 pkg-types: 2.3.0 - semver: 7.7.3 + semver: 7.7.4 simple-git: 3.30.0 sirv: 3.0.2 structured-clone-es: 1.0.0 tinyglobby: 0.2.15 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-plugin-inspect: 11.3.3(@nuxt/kit@4.2.2(magicast@0.5.1))(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) - vite-plugin-vue-tracer: 1.2.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-plugin-inspect: 11.3.3(@nuxt/kit@4.3.1(magicast@0.5.1))(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + vite-plugin-vue-tracer: 1.2.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) which: 5.0.0 - ws: 8.18.3 + ws: 8.19.0 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - vue - '@nuxt/kit@3.20.0(magicast@0.5.1)': - dependencies: - c12: 3.3.3(magicast@0.5.1) - consola: 3.4.2 - defu: 6.1.4 - destr: 2.0.5 - errx: 0.1.0 - exsolve: 1.0.8 - ignore: 7.0.5 - jiti: 2.6.1 - klona: 2.0.6 - knitwork: 1.3.0 - mlly: 1.8.0 - ohash: 2.0.11 - pathe: 2.0.3 - pkg-types: 2.3.0 - rc9: 2.1.2 - scule: 1.3.0 - semver: 7.7.3 - tinyglobby: 0.2.15 - ufo: 1.6.1 - unctx: 2.4.1 - untyped: 2.0.0 - transitivePeerDependencies: - - magicast - - '@nuxt/kit@4.2.2(magicast@0.5.1)': + '@nuxt/kit@4.3.1(magicast@0.5.1)': dependencies: c12: 3.3.3(magicast@0.5.1) consola: 3.4.2 @@ -10188,43 +10281,44 @@ snapshots: ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 - rc9: 2.1.2 + rc9: 3.0.0 scule: 1.3.0 - semver: 7.7.3 + semver: 7.7.4 tinyglobby: 0.2.15 - ufo: 1.6.1 - unctx: 2.4.1 + ufo: 1.6.3 + unctx: 2.5.0 untyped: 2.0.0 transitivePeerDependencies: - magicast - '@nuxt/nitro-server@4.2.2(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.8.2)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.2.2(6ed21839bfb37f6518a04a21db8dd1a5))(typescript@5.9.3)': + '@nuxt/nitro-server@4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.3.1(7e2836fb0faa0d6f7376f741b393f215))(typescript@5.9.3)': dependencies: '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) - '@vue/shared': 3.5.26 + '@nuxt/kit': 4.3.1(magicast@0.5.1) + '@unhead/vue': 2.1.7(vue@3.5.29(typescript@5.9.3)) + '@vue/shared': 3.5.29 consola: 3.4.2 defu: 6.1.4 destr: 2.0.5 - devalue: 5.6.1 + devalue: 5.6.3 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 - h3: 1.15.4 + h3: 1.15.5 impound: 1.0.0 klona: 2.0.6 mocked-exports: 0.1.1 - nitropack: 2.12.9(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) - nuxt: 4.2.2(6ed21839bfb37f6518a04a21db8dd1a5) + nitropack: 2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) + nuxt: 4.3.1(7e2836fb0faa0d6f7376f741b393f215) + ohash: 2.0.11 pathe: 2.0.3 pkg-types: 2.3.0 - radix3: 1.1.2 + rou3: 0.7.12 std-env: 3.10.0 - ufo: 1.6.1 - unctx: 2.4.1 - unstorage: 1.17.3(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.8.2) - vue: 3.5.26(typescript@5.9.3) + ufo: 1.6.3 + unctx: 2.5.0 + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3) + vue: 3.5.29(typescript@5.9.3) vue-bundle-renderer: 2.2.0 vue-devtools-stub: 0.1.0 transitivePeerDependencies: @@ -10262,187 +10356,55 @@ snapshots: - uploadthing - xml2js - '@nuxt/nitro-server@4.2.2(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.8.2)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(typescript@5.9.3)': + '@nuxt/schema@4.3.1': dependencies: - '@nuxt/devalue': 2.0.2 - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) - '@vue/shared': 3.5.26 - consola: 3.4.2 - defu: 6.1.4 - destr: 2.0.5 - devalue: 5.6.1 - errx: 0.1.0 - escape-string-regexp: 5.0.0 - exsolve: 1.0.8 - h3: 1.15.4 - impound: 1.0.0 - klona: 2.0.6 - mocked-exports: 0.1.1 - nitropack: 2.12.9(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) - nuxt: 4.2.2(8d53318c89c53efd506d8dd328c0edc0) - pathe: 2.0.3 - pkg-types: 2.3.0 - radix3: 1.1.2 - std-env: 3.10.0 - ufo: 1.6.1 - unctx: 2.4.1 - unstorage: 1.17.3(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.8.2) - vue: 3.5.26(typescript@5.9.3) - vue-bundle-renderer: 2.2.0 - vue-devtools-stub: 0.1.0 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@electric-sql/pglite' - - '@libsql/client' - - '@netlify/blobs' - - '@planetscale/database' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - aws4fetch - - bare-abort-controller - - better-sqlite3 - - db0 - - drizzle-orm - - encoding - - idb-keyval - - ioredis - - magicast - - mysql2 - - react-native-b4a - - rolldown - - sqlite3 - - supports-color - - typescript - - uploadthing - - xml2js - - '@nuxt/schema@4.2.2': - dependencies: - '@vue/shared': 3.5.26 + '@vue/shared': 3.5.29 defu: 6.1.4 pathe: 2.0.3 pkg-types: 2.3.0 std-env: 3.10.0 - '@nuxt/telemetry@2.6.6(magicast@0.5.1)': + '@nuxt/telemetry@2.7.0(@nuxt/kit@4.3.1(magicast@0.5.1))': dependencies: - '@nuxt/kit': 3.20.0(magicast@0.5.1) - citty: 0.1.6 + '@nuxt/kit': 4.3.1(magicast@0.5.1) + citty: 0.2.1 consola: 3.4.2 - destr: 2.0.5 - dotenv: 16.6.1 - git-url-parse: 16.1.0 - is-docker: 3.0.0 - ofetch: 1.5.1 - package-manager-detector: 1.3.0 - pathe: 2.0.3 - rc9: 2.1.2 + ofetch: 2.0.0-alpha.3 + rc9: 3.0.0 std-env: 3.10.0 - transitivePeerDependencies: - - magicast - '@nuxt/vite-builder@4.2.2(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.2.2(6ed21839bfb37f6518a04a21db8dd1a5))(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)': + '@nuxt/vite-builder@4.3.1(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(7e2836fb0faa0d6f7376f741b393f215))(optionator@0.9.4)(rollup@4.59.0)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3))(yaml@2.8.2)': dependencies: - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@rollup/plugin-replace': 6.0.3(rollup@4.52.5) - '@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) - '@vitejs/plugin-vue-jsx': 5.1.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) - autoprefixer: 10.4.23(postcss@8.5.6) + '@nuxt/kit': 4.3.1(magicast@0.5.1) + '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) + '@vitejs/plugin-vue': 6.0.4(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) + '@vitejs/plugin-vue-jsx': 5.1.4(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) + autoprefixer: 10.4.27(postcss@8.5.6) consola: 3.4.2 cssnano: 7.1.2(postcss@8.5.6) defu: 6.1.4 - esbuild: 0.27.2 + esbuild: 0.27.3 escape-string-regexp: 5.0.0 exsolve: 1.0.8 get-port-please: 3.2.0 - h3: 1.15.4 jiti: 2.6.1 knitwork: 1.3.0 magic-string: 0.30.21 mlly: 1.8.0 mocked-exports: 0.1.1 - nuxt: 4.2.2(6ed21839bfb37f6518a04a21db8dd1a5) + nuxt: 4.3.1(7e2836fb0faa0d6f7376f741b393f215) pathe: 2.0.3 pkg-types: 2.3.0 postcss: 8.5.6 - rollup-plugin-visualizer: 6.0.5(rollup@4.52.5) - seroval: 1.4.1 + rollup-plugin-visualizer: 6.0.5(rollup@4.59.0) + seroval: 1.5.0 std-env: 3.10.0 - ufo: 1.6.1 + ufo: 1.6.3 unenv: 2.0.0-rc.24 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-node: 5.2.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)) - vue: 3.5.26(typescript@5.9.3) - vue-bundle-renderer: 2.2.0 - transitivePeerDependencies: - - '@biomejs/biome' - - '@types/node' - - eslint - - less - - lightningcss - - magicast - - meow - - optionator - - oxlint - - rollup - - sass - - sass-embedded - - stylelint - - stylus - - sugarss - - supports-color - - terser - - tsx - - typescript - - vls - - vti - - vue-tsc - - yaml - - '@nuxt/vite-builder@4.2.2(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2)': - dependencies: - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@rollup/plugin-replace': 6.0.3(rollup@4.52.5) - '@vitejs/plugin-vue': 6.0.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) - '@vitejs/plugin-vue-jsx': 5.1.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) - autoprefixer: 10.4.23(postcss@8.5.6) - consola: 3.4.2 - cssnano: 7.1.2(postcss@8.5.6) - defu: 6.1.4 - esbuild: 0.27.2 - escape-string-regexp: 5.0.0 - exsolve: 1.0.8 - get-port-please: 3.2.0 - h3: 1.15.4 - jiti: 2.6.1 - knitwork: 1.3.0 - magic-string: 0.30.21 - mlly: 1.8.0 - mocked-exports: 0.1.1 - nuxt: 4.2.2(8d53318c89c53efd506d8dd328c0edc0) - pathe: 2.0.3 - pkg-types: 2.3.0 - postcss: 8.5.6 - rollup-plugin-visualizer: 6.0.5(rollup@4.52.5) - seroval: 1.4.1 - std-env: 3.10.0 - ufo: 1.6.1 - unenv: 2.0.0-rc.24 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-node: 5.2.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)) - vue: 3.5.26(typescript@5.9.3) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-node: 5.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-plugin-checker: 0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)) + vue: 3.5.29(typescript@5.9.3) vue-bundle-renderer: 2.2.0 transitivePeerDependencies: - '@biomejs/biome' @@ -10478,147 +10440,192 @@ snapshots: '@open-draft/until@2.1.0': {} - '@oxc-minify/binding-android-arm64@0.102.0': + '@oxc-minify/binding-android-arm-eabi@0.112.0': + optional: true + + '@oxc-minify/binding-android-arm64@0.112.0': + optional: true + + '@oxc-minify/binding-darwin-arm64@0.112.0': optional: true - '@oxc-minify/binding-darwin-arm64@0.102.0': + '@oxc-minify/binding-darwin-x64@0.112.0': optional: true - '@oxc-minify/binding-darwin-x64@0.102.0': + '@oxc-minify/binding-freebsd-x64@0.112.0': optional: true - '@oxc-minify/binding-freebsd-x64@0.102.0': + '@oxc-minify/binding-linux-arm-gnueabihf@0.112.0': optional: true - '@oxc-minify/binding-linux-arm-gnueabihf@0.102.0': + '@oxc-minify/binding-linux-arm-musleabihf@0.112.0': optional: true - '@oxc-minify/binding-linux-arm64-gnu@0.102.0': + '@oxc-minify/binding-linux-arm64-gnu@0.112.0': optional: true - '@oxc-minify/binding-linux-arm64-musl@0.102.0': + '@oxc-minify/binding-linux-arm64-musl@0.112.0': optional: true - '@oxc-minify/binding-linux-riscv64-gnu@0.102.0': + '@oxc-minify/binding-linux-ppc64-gnu@0.112.0': optional: true - '@oxc-minify/binding-linux-s390x-gnu@0.102.0': + '@oxc-minify/binding-linux-riscv64-gnu@0.112.0': optional: true - '@oxc-minify/binding-linux-x64-gnu@0.102.0': + '@oxc-minify/binding-linux-riscv64-musl@0.112.0': optional: true - '@oxc-minify/binding-linux-x64-musl@0.102.0': + '@oxc-minify/binding-linux-s390x-gnu@0.112.0': optional: true - '@oxc-minify/binding-openharmony-arm64@0.102.0': + '@oxc-minify/binding-linux-x64-gnu@0.112.0': optional: true - '@oxc-minify/binding-wasm32-wasi@0.102.0': + '@oxc-minify/binding-linux-x64-musl@0.112.0': + optional: true + + '@oxc-minify/binding-openharmony-arm64@0.112.0': + optional: true + + '@oxc-minify/binding-wasm32-wasi@0.112.0': dependencies: - '@napi-rs/wasm-runtime': 1.1.0 + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-minify/binding-win32-arm64-msvc@0.112.0': + optional: true + + '@oxc-minify/binding-win32-ia32-msvc@0.112.0': + optional: true + + '@oxc-minify/binding-win32-x64-msvc@0.112.0': optional: true - '@oxc-minify/binding-win32-arm64-msvc@0.102.0': + '@oxc-parser/binding-android-arm-eabi@0.112.0': optional: true - '@oxc-minify/binding-win32-x64-msvc@0.102.0': + '@oxc-parser/binding-android-arm64@0.112.0': optional: true - '@oxc-parser/binding-android-arm64@0.102.0': + '@oxc-parser/binding-darwin-arm64@0.112.0': optional: true - '@oxc-parser/binding-darwin-arm64@0.102.0': + '@oxc-parser/binding-darwin-x64@0.112.0': optional: true - '@oxc-parser/binding-darwin-x64@0.102.0': + '@oxc-parser/binding-freebsd-x64@0.112.0': optional: true - '@oxc-parser/binding-freebsd-x64@0.102.0': + '@oxc-parser/binding-linux-arm-gnueabihf@0.112.0': optional: true - '@oxc-parser/binding-linux-arm-gnueabihf@0.102.0': + '@oxc-parser/binding-linux-arm-musleabihf@0.112.0': optional: true - '@oxc-parser/binding-linux-arm64-gnu@0.102.0': + '@oxc-parser/binding-linux-arm64-gnu@0.112.0': optional: true - '@oxc-parser/binding-linux-arm64-musl@0.102.0': + '@oxc-parser/binding-linux-arm64-musl@0.112.0': optional: true - '@oxc-parser/binding-linux-riscv64-gnu@0.102.0': + '@oxc-parser/binding-linux-ppc64-gnu@0.112.0': optional: true - '@oxc-parser/binding-linux-s390x-gnu@0.102.0': + '@oxc-parser/binding-linux-riscv64-gnu@0.112.0': optional: true - '@oxc-parser/binding-linux-x64-gnu@0.102.0': + '@oxc-parser/binding-linux-riscv64-musl@0.112.0': optional: true - '@oxc-parser/binding-linux-x64-musl@0.102.0': + '@oxc-parser/binding-linux-s390x-gnu@0.112.0': optional: true - '@oxc-parser/binding-openharmony-arm64@0.102.0': + '@oxc-parser/binding-linux-x64-gnu@0.112.0': optional: true - '@oxc-parser/binding-wasm32-wasi@0.102.0': + '@oxc-parser/binding-linux-x64-musl@0.112.0': + optional: true + + '@oxc-parser/binding-openharmony-arm64@0.112.0': + optional: true + + '@oxc-parser/binding-wasm32-wasi@0.112.0': dependencies: - '@napi-rs/wasm-runtime': 1.1.0 + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-parser/binding-win32-arm64-msvc@0.112.0': optional: true - '@oxc-parser/binding-win32-arm64-msvc@0.102.0': + '@oxc-parser/binding-win32-ia32-msvc@0.112.0': optional: true - '@oxc-parser/binding-win32-x64-msvc@0.102.0': + '@oxc-parser/binding-win32-x64-msvc@0.112.0': optional: true - '@oxc-project/types@0.102.0': {} + '@oxc-project/types@0.112.0': {} + + '@oxc-transform/binding-android-arm-eabi@0.112.0': + optional: true + + '@oxc-transform/binding-android-arm64@0.112.0': + optional: true - '@oxc-transform/binding-android-arm64@0.102.0': + '@oxc-transform/binding-darwin-arm64@0.112.0': optional: true - '@oxc-transform/binding-darwin-arm64@0.102.0': + '@oxc-transform/binding-darwin-x64@0.112.0': optional: true - '@oxc-transform/binding-darwin-x64@0.102.0': + '@oxc-transform/binding-freebsd-x64@0.112.0': optional: true - '@oxc-transform/binding-freebsd-x64@0.102.0': + '@oxc-transform/binding-linux-arm-gnueabihf@0.112.0': optional: true - '@oxc-transform/binding-linux-arm-gnueabihf@0.102.0': + '@oxc-transform/binding-linux-arm-musleabihf@0.112.0': optional: true - '@oxc-transform/binding-linux-arm64-gnu@0.102.0': + '@oxc-transform/binding-linux-arm64-gnu@0.112.0': optional: true - '@oxc-transform/binding-linux-arm64-musl@0.102.0': + '@oxc-transform/binding-linux-arm64-musl@0.112.0': optional: true - '@oxc-transform/binding-linux-riscv64-gnu@0.102.0': + '@oxc-transform/binding-linux-ppc64-gnu@0.112.0': optional: true - '@oxc-transform/binding-linux-s390x-gnu@0.102.0': + '@oxc-transform/binding-linux-riscv64-gnu@0.112.0': optional: true - '@oxc-transform/binding-linux-x64-gnu@0.102.0': + '@oxc-transform/binding-linux-riscv64-musl@0.112.0': optional: true - '@oxc-transform/binding-linux-x64-musl@0.102.0': + '@oxc-transform/binding-linux-s390x-gnu@0.112.0': optional: true - '@oxc-transform/binding-openharmony-arm64@0.102.0': + '@oxc-transform/binding-linux-x64-gnu@0.112.0': optional: true - '@oxc-transform/binding-wasm32-wasi@0.102.0': + '@oxc-transform/binding-linux-x64-musl@0.112.0': + optional: true + + '@oxc-transform/binding-openharmony-arm64@0.112.0': + optional: true + + '@oxc-transform/binding-wasm32-wasi@0.112.0': dependencies: - '@napi-rs/wasm-runtime': 1.1.0 + '@napi-rs/wasm-runtime': 1.1.1 + optional: true + + '@oxc-transform/binding-win32-arm64-msvc@0.112.0': optional: true - '@oxc-transform/binding-win32-arm64-msvc@0.102.0': + '@oxc-transform/binding-win32-ia32-msvc@0.112.0': optional: true - '@oxc-transform/binding-win32-x64-msvc@0.102.0': + '@oxc-transform/binding-win32-x64-msvc@0.112.0': optional: true '@paralleldrive/cuid2@2.2.2': @@ -10753,17 +10760,17 @@ snapshots: dependencies: '@prisma/debug': 6.19.0 - '@rolldown/pluginutils@1.0.0-beta.53': {} + '@rolldown/pluginutils@1.0.0-rc.2': {} - '@rolldown/pluginutils@1.0.0-beta.57': {} + '@rolldown/pluginutils@1.0.0-rc.5': {} - '@rollup/plugin-alias@5.1.1(rollup@4.52.5)': + '@rollup/plugin-alias@6.0.0(rollup@4.59.0)': optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/plugin-commonjs@28.0.9(rollup@4.52.5)': + '@rollup/plugin-commonjs@29.0.0(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) commondir: 1.0.1 estree-walker: 2.0.2 fdir: 6.5.0(picomatch@4.0.3) @@ -10771,179 +10778,128 @@ snapshots: magic-string: 0.30.21 picomatch: 4.0.3 optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/plugin-inject@5.0.5(rollup@4.52.5)': + '@rollup/plugin-inject@5.0.5(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) estree-walker: 2.0.2 magic-string: 0.30.21 optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/plugin-json@6.1.0(rollup@4.52.5)': + '@rollup/plugin-json@6.1.0(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/plugin-node-resolve@16.0.3(rollup@4.52.5)': + '@rollup/plugin-node-resolve@16.0.3(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) '@types/resolve': 1.20.2 deepmerge: 4.3.1 is-module: 1.0.0 resolve: 1.22.11 optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/plugin-replace@6.0.3(rollup@4.52.5)': + '@rollup/plugin-replace@6.0.3(rollup@4.59.0)': dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) magic-string: 0.30.21 optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/plugin-terser@0.4.4(rollup@4.52.5)': + '@rollup/plugin-terser@0.4.4(rollup@4.59.0)': dependencies: serialize-javascript: 6.0.2 smob: 1.5.0 terser: 5.44.0 optionalDependencies: - rollup: 4.52.5 + rollup: 4.59.0 - '@rollup/pluginutils@5.3.0(rollup@4.52.5)': + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': dependencies: '@types/estree': 1.0.8 estree-walker: 2.0.2 picomatch: 4.0.3 optionalDependencies: - rollup: 4.52.5 - - '@rollup/rollup-android-arm-eabi@4.44.0': - optional: true - - '@rollup/rollup-android-arm-eabi@4.52.5': - optional: true - - '@rollup/rollup-android-arm64@4.44.0': - optional: true - - '@rollup/rollup-android-arm64@4.52.5': - optional: true - - '@rollup/rollup-darwin-arm64@4.44.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.52.5': - optional: true - - '@rollup/rollup-darwin-x64@4.44.0': - optional: true - - '@rollup/rollup-darwin-x64@4.52.5': - optional: true - - '@rollup/rollup-freebsd-arm64@4.44.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.52.5': - optional: true - - '@rollup/rollup-freebsd-x64@4.44.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.52.5': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.44.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.52.5': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.44.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.52.5': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.44.0': - optional: true + rollup: 4.59.0 - '@rollup/rollup-linux-arm64-gnu@4.52.5': + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.44.0': + '@rollup/rollup-android-arm64@4.59.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.52.5': + '@rollup/rollup-darwin-arm64@4.59.0': optional: true - '@rollup/rollup-linux-loong64-gnu@4.52.5': + '@rollup/rollup-darwin-x64@4.59.0': optional: true - '@rollup/rollup-linux-loongarch64-gnu@4.44.0': + '@rollup/rollup-freebsd-arm64@4.59.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.44.0': + '@rollup/rollup-freebsd-x64@4.59.0': optional: true - '@rollup/rollup-linux-ppc64-gnu@4.52.5': + '@rollup/rollup-linux-arm-gnueabihf@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.44.0': + '@rollup/rollup-linux-arm-musleabihf@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.52.5': + '@rollup/rollup-linux-arm64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.44.0': + '@rollup/rollup-linux-arm64-musl@4.59.0': optional: true - '@rollup/rollup-linux-riscv64-musl@4.52.5': + '@rollup/rollup-linux-loong64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.44.0': + '@rollup/rollup-linux-loong64-musl@4.59.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.52.5': + '@rollup/rollup-linux-ppc64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.44.0': + '@rollup/rollup-linux-ppc64-musl@4.59.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.52.5': + '@rollup/rollup-linux-riscv64-gnu@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.44.0': + '@rollup/rollup-linux-riscv64-musl@4.59.0': optional: true - '@rollup/rollup-linux-x64-musl@4.52.5': + '@rollup/rollup-linux-s390x-gnu@4.59.0': optional: true - '@rollup/rollup-openharmony-arm64@4.52.5': + '@rollup/rollup-linux-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.44.0': + '@rollup/rollup-linux-x64-musl@4.59.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.52.5': + '@rollup/rollup-openbsd-x64@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.44.0': + '@rollup/rollup-openharmony-arm64@4.59.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.52.5': + '@rollup/rollup-win32-arm64-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-gnu@4.52.5': + '@rollup/rollup-win32-ia32-msvc@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.44.0': + '@rollup/rollup-win32-x64-gnu@4.59.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.52.5': + '@rollup/rollup-win32-x64-msvc@4.59.0': optional: true '@rtsao/scc@1.1.0': {} @@ -10960,126 +10916,104 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@sveltejs/acorn-typescript@1.0.6(acorn@8.15.0)': - dependencies: - acorn: 8.15.0 - '@sveltejs/acorn-typescript@1.0.9(acorn@8.15.0)': dependencies: acorn: 8.15.0 - '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))': + '@sveltejs/adapter-auto@7.0.0(@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))': dependencies: - '@sveltejs/kit': 2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) - '@sveltejs/kit@2.48.3(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) - '@types/cookie': 0.6.0 - acorn: 8.15.0 - cookie: 0.6.0 - devalue: 5.4.2 - esm-env: 1.2.2 - kleur: 4.1.5 - magic-string: 0.30.21 - mrmime: 2.0.1 - sade: 1.8.1 - set-cookie-parser: 2.7.2 - sirv: 3.0.2 - svelte: 5.46.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - - '@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': - dependencies: - '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@types/cookie': 0.6.0 acorn: 8.15.0 - cookie: 0.6.0 - devalue: 5.6.1 + cookie: 1.1.1 + devalue: 5.6.3 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 - sade: 1.8.1 - set-cookie-parser: 2.7.2 + set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.45.6 + svelte: 5.53.5 vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + optionalDependencies: + typescript: 5.9.3 - '@sveltejs/kit@2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@sveltejs/kit@2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: '@standard-schema/spec': 1.0.0 - '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) '@types/cookie': 0.6.0 acorn: 8.15.0 - cookie: 0.6.0 - devalue: 5.6.1 + cookie: 1.1.1 + devalue: 5.6.3 esm-env: 1.2.2 kleur: 4.1.5 magic-string: 0.30.21 mrmime: 2.0.1 - sade: 1.8.1 - set-cookie-parser: 2.7.2 + set-cookie-parser: 3.0.1 sirv: 3.0.2 - svelte: 5.46.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - optional: true + svelte: 5.53.5 + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + optionalDependencies: + typescript: 5.9.3 - '@sveltejs/package@2.5.7(svelte@5.45.6)(typescript@5.9.3)': + '@sveltejs/package@2.5.7(svelte@5.53.5)(typescript@5.9.3)': dependencies: chokidar: 5.0.0 kleur: 4.1.5 sade: 1.8.1 semver: 7.7.3 - svelte: 5.45.6 - svelte2tsx: 0.7.46(svelte@5.45.6)(typescript@5.9.3) + svelte: 5.53.5 + svelte2tsx: 0.7.46(svelte@5.53.5)(typescript@5.9.3) transitivePeerDependencies: - typescript - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) debug: 4.4.3 - svelte: 5.45.6 + svelte: 5.53.5 vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte-inspector@5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte': 6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) debug: 4.4.3 - svelte: 5.46.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + svelte: 5.53.5 + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.45.6)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 - svelte: 5.45.6 + svelte: 5.53.5 vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) vitefu: 1.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) transitivePeerDependencies: - supports-color - '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + '@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': dependencies: - '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/vite-plugin-svelte-inspector': 5.0.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) debug: 4.4.3 deepmerge: 4.3.1 magic-string: 0.30.21 - svelte: 5.46.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vitefu: 1.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + svelte: 5.53.5 + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vitefu: 1.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) transitivePeerDependencies: - supports-color @@ -11279,6 +11213,13 @@ snapshots: tailwindcss: 4.1.18 vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + '@tailwindcss/vite@4.1.18(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))': + dependencies: + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + tailwindcss: 4.1.18 + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + '@tanstack/match-sorter-utils@8.19.4': dependencies: remove-accents: 0.5.0 @@ -11292,10 +11233,10 @@ snapshots: '@tanstack/query-core': 5.90.2 react: 19.2.0 - '@tanstack/svelte-query@6.0.10(svelte@5.45.6)': + '@tanstack/svelte-query@6.0.10(svelte@5.53.5)': dependencies: '@tanstack/query-core': 5.90.12 - svelte: 5.45.6 + svelte: 5.53.5 '@tanstack/vue-query@5.90.2(vue@3.5.22(typescript@5.9.3))': dependencies: @@ -11405,10 +11346,6 @@ snapshots: dependencies: undici-types: 6.21.0 - '@types/parse-path@7.1.0': - dependencies: - parse-path: 7.1.0 - '@types/pg@8.11.11': dependencies: '@types/node': 20.17.24 @@ -11481,6 +11418,8 @@ snapshots: '@types/toposort@2.0.7': {} + '@types/trusted-types@2.0.7': {} + '@types/vscode@1.101.0': {} '@types/whatwg-mimetype@3.0.2': {} @@ -11547,7 +11486,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.34.1(typescript@5.9.3) '@typescript-eslint/types': 8.34.1 - debug: 4.4.1 + debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -11556,7 +11495,7 @@ snapshots: dependencies: '@typescript-eslint/tsconfig-utils': 8.46.2(typescript@5.9.3) '@typescript-eslint/types': 8.46.2 - debug: 4.4.1 + debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -11615,7 +11554,7 @@ snapshots: debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 - minimatch: 9.0.5 + minimatch: 9.0.8 semver: 7.7.3 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 @@ -11631,7 +11570,7 @@ snapshots: debug: 4.4.1 fast-glob: 3.3.3 is-glob: 4.0.3 - minimatch: 9.0.5 + minimatch: 9.0.8 semver: 7.7.3 ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 @@ -11670,11 +11609,11 @@ snapshots: '@typescript-eslint/types': 8.46.2 eslint-visitor-keys: 4.2.1 - '@unhead/vue@2.0.19(vue@3.5.26(typescript@5.9.3))': + '@unhead/vue@2.1.7(vue@3.5.29(typescript@5.9.3))': dependencies: - hookable: 5.5.3 - unhead: 2.0.19 - vue: 3.5.26(typescript@5.9.3) + hookable: 6.0.1 + unhead: 2.1.7 + vue: 3.5.29(typescript@5.9.3) '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -11735,16 +11674,16 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - '@vercel/nft@0.30.3(rollup@4.52.5)': + '@vercel/nft@1.3.2(rollup@4.59.0)': dependencies: '@mapbox/node-pre-gyp': 2.0.0 - '@rollup/pluginutils': 5.3.0(rollup@4.52.5) + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) acorn: 8.15.0 acorn-import-attributes: 1.9.5(acorn@8.15.0) async-sema: 3.1.1 bindings: 1.5.0 estree-walker: 2.0.2 - glob: 10.4.5 + glob: 13.0.6 graceful-fs: 4.2.11 node-gyp-build: 4.8.4 picomatch: 4.0.3 @@ -11754,23 +11693,23 @@ snapshots: - rollup - supports-color - '@vitejs/plugin-vue-jsx@5.1.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + '@vitejs/plugin-vue-jsx@5.1.4(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': dependencies: - '@babel/core': 7.28.5 - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.5) - '@babel/plugin-transform-typescript': 7.28.5(@babel/core@7.28.5) - '@rolldown/pluginutils': 1.0.0-beta.57 - '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.28.5) - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vue: 3.5.26(typescript@5.9.3) + '@babel/core': 7.29.0 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + '@rolldown/pluginutils': 1.0.0-rc.5 + '@vue/babel-plugin-jsx': 2.0.1(@babel/core@7.29.0) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vue: 3.5.29(typescript@5.9.3) transitivePeerDependencies: - supports-color - '@vitejs/plugin-vue@6.0.3(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + '@vitejs/plugin-vue@6.0.4(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': dependencies: - '@rolldown/pluginutils': 1.0.0-beta.53 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vue: 3.5.26(typescript@5.9.3) + '@rolldown/pluginutils': 1.0.0-rc.2 + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vue: 3.5.29(typescript@5.9.3) '@vitest/coverage-v8@4.0.16(vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0))': dependencies: @@ -11846,16 +11785,10 @@ snapshots: '@vitest/pretty-format': 4.0.16 tinyrainbow: 3.0.3 - '@volar/language-core@2.4.27': - dependencies: - '@volar/source-map': 2.4.27 - '@volar/language-core@2.4.28': dependencies: '@volar/source-map': 2.4.28 - '@volar/source-map@2.4.27': {} - '@volar/source-map@2.4.28': {} '@volar/typescript@2.4.28': @@ -11864,7 +11797,7 @@ snapshots: path-browserify: 1.0.1 vscode-uri: 3.1.0 - '@vue-macros/common@3.1.1(vue@3.5.26(typescript@5.9.3))': + '@vue-macros/common@3.1.1(vue@3.5.29(typescript@5.9.3))': dependencies: '@vue/compiler-sfc': 3.5.26 ast-kit: 2.1.3 @@ -11872,33 +11805,33 @@ snapshots: magic-string-ast: 1.0.3 unplugin-utils: 0.3.1 optionalDependencies: - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.29(typescript@5.9.3) '@vue/babel-helper-vue-transform-on@2.0.1': {} - '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.28.5)': + '@vue/babel-plugin-jsx@2.0.1(@babel/core@7.29.0)': dependencies: - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5) - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.5 - '@babel/types': 7.28.5 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/template': 7.28.6 + '@babel/traverse': 7.28.6 + '@babel/types': 7.29.0 '@vue/babel-helper-vue-transform-on': 2.0.1 - '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.28.5) - '@vue/shared': 3.5.26 + '@vue/babel-plugin-resolve-type': 2.0.1(@babel/core@7.29.0) + '@vue/shared': 3.5.29 optionalDependencies: - '@babel/core': 7.28.5 + '@babel/core': 7.29.0 transitivePeerDependencies: - supports-color - '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.28.5)': + '@vue/babel-plugin-resolve-type@2.0.1(@babel/core@7.29.0)': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/core': 7.28.5 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-plugin-utils': 7.27.1 - '@babel/parser': 7.28.5 + '@babel/code-frame': 7.28.6 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/parser': 7.29.0 '@vue/compiler-sfc': 3.5.26 transitivePeerDependencies: - supports-color @@ -11919,6 +11852,14 @@ snapshots: estree-walker: 2.0.2 source-map-js: 1.2.1 + '@vue/compiler-core@3.5.29': + dependencies: + '@babel/parser': 7.29.0 + '@vue/shared': 3.5.29 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + '@vue/compiler-dom@3.5.22': dependencies: '@vue/compiler-core': 3.5.22 @@ -11929,6 +11870,11 @@ snapshots: '@vue/compiler-core': 3.5.26 '@vue/shared': 3.5.26 + '@vue/compiler-dom@3.5.29': + dependencies: + '@vue/compiler-core': 3.5.29 + '@vue/shared': 3.5.29 + '@vue/compiler-sfc@3.5.22': dependencies: '@babel/parser': 7.28.5 @@ -11943,7 +11889,7 @@ snapshots: '@vue/compiler-sfc@3.5.26': dependencies: - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.0 '@vue/compiler-core': 3.5.26 '@vue/compiler-dom': 3.5.26 '@vue/compiler-ssr': 3.5.26 @@ -11953,6 +11899,18 @@ snapshots: postcss: 8.5.6 source-map-js: 1.2.1 + '@vue/compiler-sfc@3.5.29': + dependencies: + '@babel/parser': 7.29.0 + '@vue/compiler-core': 3.5.29 + '@vue/compiler-dom': 3.5.29 + '@vue/compiler-ssr': 3.5.29 + '@vue/shared': 3.5.29 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.6 + source-map-js: 1.2.1 + '@vue/compiler-ssr@3.5.22': dependencies: '@vue/compiler-dom': 3.5.22 @@ -11963,17 +11921,22 @@ snapshots: '@vue/compiler-dom': 3.5.26 '@vue/shared': 3.5.26 + '@vue/compiler-ssr@3.5.29': + dependencies: + '@vue/compiler-dom': 3.5.29 + '@vue/shared': 3.5.29 + '@vue/devtools-api@6.6.4': {} - '@vue/devtools-core@8.0.5(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3))': + '@vue/devtools-core@8.0.5(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3))': dependencies: '@vue/devtools-kit': 8.0.5 '@vue/devtools-shared': 8.0.5 mitt: 3.0.1 nanoid: 5.1.6 pathe: 2.0.3 - vite-hot-client: 2.1.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) - vue: 3.5.26(typescript@5.9.3) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + vue: 3.5.29(typescript@5.9.3) transitivePeerDependencies: - vite @@ -11983,7 +11946,7 @@ snapshots: birpc: 2.9.0 hookable: 5.5.3 mitt: 3.0.1 - perfect-debounce: 2.0.0 + perfect-debounce: 2.1.0 speakingurl: 14.0.1 superjson: 2.2.3 @@ -11991,16 +11954,6 @@ snapshots: dependencies: rfdc: 1.4.1 - '@vue/language-core@3.2.1': - dependencies: - '@volar/language-core': 2.4.27 - '@vue/compiler-dom': 3.5.26 - '@vue/shared': 3.5.26 - alien-signals: 3.0.3 - muggle-string: 0.4.1 - path-browserify: 1.0.1 - picomatch: 4.0.3 - '@vue/language-core@3.2.5': dependencies: '@volar/language-core': 2.4.28 @@ -12015,19 +11968,19 @@ snapshots: dependencies: '@vue/shared': 3.5.22 - '@vue/reactivity@3.5.26': + '@vue/reactivity@3.5.29': dependencies: - '@vue/shared': 3.5.26 + '@vue/shared': 3.5.29 '@vue/runtime-core@3.5.22': dependencies: '@vue/reactivity': 3.5.22 '@vue/shared': 3.5.22 - '@vue/runtime-core@3.5.26': + '@vue/runtime-core@3.5.29': dependencies: - '@vue/reactivity': 3.5.26 - '@vue/shared': 3.5.26 + '@vue/reactivity': 3.5.29 + '@vue/shared': 3.5.29 '@vue/runtime-dom@3.5.22': dependencies: @@ -12036,11 +11989,11 @@ snapshots: '@vue/shared': 3.5.22 csstype: 3.2.3 - '@vue/runtime-dom@3.5.26': + '@vue/runtime-dom@3.5.29': dependencies: - '@vue/reactivity': 3.5.26 - '@vue/runtime-core': 3.5.26 - '@vue/shared': 3.5.26 + '@vue/reactivity': 3.5.29 + '@vue/runtime-core': 3.5.29 + '@vue/shared': 3.5.29 csstype: 3.2.3 '@vue/server-renderer@3.5.22(vue@3.5.22(typescript@5.9.3))': @@ -12049,16 +12002,18 @@ snapshots: '@vue/shared': 3.5.22 vue: 3.5.22(typescript@5.9.3) - '@vue/server-renderer@3.5.26(vue@3.5.26(typescript@5.9.3))': + '@vue/server-renderer@3.5.29(vue@3.5.29(typescript@5.9.3))': dependencies: - '@vue/compiler-ssr': 3.5.26 - '@vue/shared': 3.5.26 - vue: 3.5.26(typescript@5.9.3) + '@vue/compiler-ssr': 3.5.29 + '@vue/shared': 3.5.29 + vue: 3.5.29(typescript@5.9.3) '@vue/shared@3.5.22': {} '@vue/shared@3.5.26': {} + '@vue/shared@3.5.29': {} + abbrev@3.0.1: {} abort-controller@3.0.0: @@ -12090,18 +12045,18 @@ snapshots: agent-base@7.1.4: {} - ajv-formats@3.0.1(ajv@8.17.1): + ajv-formats@3.0.1(ajv@8.18.0): optionalDependencies: - ajv: 8.17.1 + ajv: 8.18.0 - ajv@6.12.6: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 json-schema-traverse: 0.4.1 uri-js: 4.4.1 - ajv@8.17.1: + ajv@8.18.0: dependencies: fast-deep-equal: 3.1.3 fast-uri: 3.1.0 @@ -12137,11 +12092,11 @@ snapshots: archiver-utils@5.0.2: dependencies: - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 is-stream: 2.0.1 lazystream: 1.0.1 - lodash: 4.17.21 + lodash: 4.17.23 normalize-path: 3.0.0 readable-stream: 4.7.0 @@ -12164,6 +12119,8 @@ snapshots: dependencies: dequal: 2.0.3 + aria-query@5.3.1: {} + aria-query@5.3.2: {} array-buffer-byte-length@1.0.2: @@ -12237,7 +12194,7 @@ snapshots: ast-kit@2.1.3: dependencies: - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.0 pathe: 2.0.3 ast-types-flow@0.0.8: {} @@ -12250,7 +12207,7 @@ snapshots: ast-walker-scope@0.8.3: dependencies: - '@babel/parser': 7.28.5 + '@babel/parser': 7.29.0 ast-kit: 2.1.3 async-function@1.0.0: {} @@ -12263,10 +12220,10 @@ snapshots: atomic-sleep@1.0.0: {} - autoprefixer@10.4.23(postcss@8.5.6): + autoprefixer@10.4.27(postcss@8.5.6): dependencies: browserslist: 4.28.1 - caniuse-lite: 1.0.30001761 + caniuse-lite: 1.0.30001774 fraction.js: 5.3.4 picocolors: 1.1.1 postcss: 8.5.6 @@ -12291,6 +12248,8 @@ snapshots: balanced-match@1.0.2: {} + balanced-match@4.0.4: {} + bare-events@2.8.1: {} base64-js@1.5.1: {} @@ -12299,10 +12258,10 @@ snapshots: baseline-browser-mapping@2.9.11: {} - better-auth@1.4.17(16db68be254775c88fdefd75497fac00): + better-auth@1.4.19(824ab937a8901dc918eb211f1d922b51): dependencies: - '@better-auth/core': 1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) - '@better-auth/telemetry': 1.4.17(@better-auth/core@1.4.17(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)) + '@better-auth/core': 1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1) + '@better-auth/telemetry': 1.4.19(@better-auth/core@1.4.19(@better-auth/utils@0.3.0)(@better-fetch/fetch@1.1.21)(better-call@1.1.8(zod@4.3.6))(jose@6.1.2)(kysely@0.28.8)(nanostores@1.0.1)) '@better-auth/utils': 0.3.0 '@better-fetch/fetch': 1.1.21 '@noble/ciphers': 2.0.1 @@ -12315,18 +12274,18 @@ snapshots: zod: 4.3.6 optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)) - '@sveltejs/kit': 2.49.1(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.46.1)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + '@sveltejs/kit': 2.53.2(@sveltejs/vite-plugin-svelte@6.2.1(svelte@5.53.5)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)))(svelte@5.53.5)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) better-sqlite3: 12.5.0 drizzle-orm: 0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0) mysql2: 3.16.1 - next: 16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + next: 16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) pg: 8.16.3 prisma: 6.19.0(magicast@0.5.1)(typescript@5.9.3) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - svelte: 5.46.1 + svelte: 5.53.5 vitest: 4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.29(typescript@5.9.3) better-call@1.1.8(zod@4.3.6): dependencies: @@ -12359,15 +12318,15 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - body-parser@2.2.0: + body-parser@2.2.2: dependencies: bytes: 3.1.2 content-type: 1.0.5 - debug: 4.4.1 + debug: 4.4.3 http-errors: 2.0.0 - iconv-lite: 0.6.3 + iconv-lite: 0.7.0 on-finished: 2.4.1 - qs: 6.14.0 + qs: 6.15.0 raw-body: 3.0.1 type-is: 2.0.1 transitivePeerDependencies: @@ -12380,13 +12339,13 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.1: + brace-expansion@2.0.2: dependencies: balanced-match: 1.0.2 - brace-expansion@2.0.2: + brace-expansion@5.0.3: dependencies: - balanced-match: 1.0.2 + balanced-match: 4.0.4 braces@3.0.3: dependencies: @@ -12403,7 +12362,7 @@ snapshots: browserslist@4.28.1: dependencies: baseline-browser-mapping: 2.9.11 - caniuse-lite: 1.0.30001761 + caniuse-lite: 1.0.30001774 electron-to-chromium: 1.5.267 node-releases: 2.0.27 update-browserslist-db: 1.2.3(browserslist@4.28.1) @@ -12519,6 +12478,8 @@ snapshots: caniuse-lite@1.0.30001761: {} + caniuse-lite@1.0.30001774: {} + chai@6.2.1: {} chalk@2.4.2: @@ -12539,7 +12500,7 @@ snapshots: chevrotain-allstar@0.3.1(chevrotain@11.0.3): dependencies: chevrotain: 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 chevrotain@10.5.0: dependencies: @@ -12547,7 +12508,7 @@ snapshots: '@chevrotain/gast': 10.5.0 '@chevrotain/types': 10.5.0 '@chevrotain/utils': 10.5.0 - lodash: 4.17.21 + lodash: 4.17.23 regexp-to-ast: 0.5.0 chevrotain@11.0.3: @@ -12557,7 +12518,7 @@ snapshots: '@chevrotain/regexp-to-ast': 11.0.3 '@chevrotain/types': 11.0.3 '@chevrotain/utils': 11.0.3 - lodash-es: 4.17.21 + lodash-es: 4.17.23 chokidar@4.0.3: dependencies: @@ -12575,6 +12536,8 @@ snapshots: dependencies: consola: 3.4.2 + citty@0.2.1: {} + cli-cursor@3.1.0: dependencies: restore-cursor: 3.1.0 @@ -12655,6 +12618,8 @@ snapshots: confbox@0.2.2: {} + confbox@0.2.4: {} + consola@3.4.2: {} content-disposition@1.0.0: @@ -12671,8 +12636,6 @@ snapshots: cookie-signature@1.2.2: {} - cookie@0.6.0: {} - cookie@0.7.1: {} cookie@1.1.1: {} @@ -12910,18 +12873,14 @@ snapshots: detect-libc@2.1.2: {} - devalue@5.4.2: {} - - devalue@5.6.1: {} - - devalue@5.6.2: {} + devalue@5.6.3: {} dezalgo@1.0.4: dependencies: asap: 2.0.6 wrappy: 1.0.2 - diff@8.0.2: {} + diff@8.0.3: {} doctrine@2.1.0: dependencies: @@ -13026,6 +12985,8 @@ snapshots: entities@7.0.0: {} + entities@7.0.1: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -13116,6 +13077,8 @@ snapshots: es-module-lexer@1.7.0: {} + es-module-lexer@2.0.0: {} + es-object-atoms@1.1.1: dependencies: es-errors: 1.3.0 @@ -13137,35 +13100,6 @@ snapshots: is-date-object: 1.1.0 is-symbol: 1.1.1 - esbuild@0.25.11: - optionalDependencies: - '@esbuild/aix-ppc64': 0.25.11 - '@esbuild/android-arm': 0.25.11 - '@esbuild/android-arm64': 0.25.11 - '@esbuild/android-x64': 0.25.11 - '@esbuild/darwin-arm64': 0.25.11 - '@esbuild/darwin-x64': 0.25.11 - '@esbuild/freebsd-arm64': 0.25.11 - '@esbuild/freebsd-x64': 0.25.11 - '@esbuild/linux-arm': 0.25.11 - '@esbuild/linux-arm64': 0.25.11 - '@esbuild/linux-ia32': 0.25.11 - '@esbuild/linux-loong64': 0.25.11 - '@esbuild/linux-mips64el': 0.25.11 - '@esbuild/linux-ppc64': 0.25.11 - '@esbuild/linux-riscv64': 0.25.11 - '@esbuild/linux-s390x': 0.25.11 - '@esbuild/linux-x64': 0.25.11 - '@esbuild/netbsd-arm64': 0.25.11 - '@esbuild/netbsd-x64': 0.25.11 - '@esbuild/openbsd-arm64': 0.25.11 - '@esbuild/openbsd-x64': 0.25.11 - '@esbuild/openharmony-arm64': 0.25.11 - '@esbuild/sunos-x64': 0.25.11 - '@esbuild/win32-arm64': 0.25.11 - '@esbuild/win32-ia32': 0.25.11 - '@esbuild/win32-x64': 0.25.11 - esbuild@0.25.5: optionalDependencies: '@esbuild/aix-ppc64': 0.25.5 @@ -13223,6 +13157,35 @@ snapshots: '@esbuild/win32-ia32': 0.27.2 '@esbuild/win32-x64': 0.27.2 + esbuild@0.27.3: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.3 + '@esbuild/android-arm': 0.27.3 + '@esbuild/android-arm64': 0.27.3 + '@esbuild/android-x64': 0.27.3 + '@esbuild/darwin-arm64': 0.27.3 + '@esbuild/darwin-x64': 0.27.3 + '@esbuild/freebsd-arm64': 0.27.3 + '@esbuild/freebsd-x64': 0.27.3 + '@esbuild/linux-arm': 0.27.3 + '@esbuild/linux-arm64': 0.27.3 + '@esbuild/linux-ia32': 0.27.3 + '@esbuild/linux-loong64': 0.27.3 + '@esbuild/linux-mips64el': 0.27.3 + '@esbuild/linux-ppc64': 0.27.3 + '@esbuild/linux-riscv64': 0.27.3 + '@esbuild/linux-s390x': 0.27.3 + '@esbuild/linux-x64': 0.27.3 + '@esbuild/netbsd-arm64': 0.27.3 + '@esbuild/netbsd-x64': 0.27.3 + '@esbuild/openbsd-arm64': 0.27.3 + '@esbuild/openbsd-x64': 0.27.3 + '@esbuild/openharmony-arm64': 0.27.3 + '@esbuild/sunos-x64': 0.27.3 + '@esbuild/win32-arm64': 0.27.3 + '@esbuild/win32-ia32': 0.27.3 + '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} escape-html@1.0.3: {} @@ -13239,7 +13202,7 @@ snapshots: eslint: 9.29.0(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.29.0(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.29.0(jiti@2.6.1)) @@ -13272,7 +13235,7 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)) transitivePeerDependencies: - supports-color @@ -13287,7 +13250,7 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.29.0(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.46.2(eslint@9.29.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)))(eslint@9.29.0(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -13302,7 +13265,7 @@ snapshots: hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 - minimatch: 3.1.2 + minimatch: 3.1.5 object.fromentries: 2.0.8 object.groupby: 1.0.3 object.values: 1.2.1 @@ -13330,7 +13293,7 @@ snapshots: hasown: 2.0.2 jsx-ast-utils: 3.3.5 language-tags: 1.0.9 - minimatch: 3.1.2 + minimatch: 3.1.5 object.fromentries: 2.0.8 safe-regex-test: 1.1.0 string.prototype.includes: 2.0.1 @@ -13358,7 +13321,7 @@ snapshots: estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 - minimatch: 3.1.2 + minimatch: 3.1.5 object.entries: 1.1.9 object.fromentries: 2.0.8 object.values: 1.2.1 @@ -13386,13 +13349,13 @@ snapshots: '@eslint/core': 0.14.0 '@eslint/eslintrc': 3.3.1 '@eslint/js': 9.29.0 - '@eslint/plugin-kit': 0.3.2 + '@eslint/plugin-kit': 0.3.5 '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 '@types/json-schema': 7.0.15 - ajv: 6.12.6 + ajv: 6.14.0 chalk: 4.1.2 cross-spawn: 7.0.6 debug: 4.4.1 @@ -13411,7 +13374,7 @@ snapshots: is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 3.1.5 natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: @@ -13431,10 +13394,6 @@ snapshots: dependencies: estraverse: 5.3.0 - esrap@2.2.1: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - esrap@2.2.3: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -13503,7 +13462,7 @@ snapshots: express@5.1.0: dependencies: accepts: 2.0.0 - body-parser: 2.2.0 + body-parser: 2.2.2 content-disposition: 1.0.0 content-type: 1.0.5 cookie: 0.7.1 @@ -13521,7 +13480,7 @@ snapshots: once: 1.4.0 parseurl: 1.3.3 proxy-addr: 2.0.7 - qs: 6.14.0 + qs: 6.15.0 range-parser: 1.2.1 router: 2.2.0 send: 1.2.0 @@ -13567,8 +13526,8 @@ snapshots: fast-json-stringify@6.1.1: dependencies: '@fastify/merge-json-schemas': 0.2.1 - ajv: 8.17.1 - ajv-formats: 3.0.1(ajv@8.17.1) + ajv: 8.18.0 + ajv-formats: 3.0.1(ajv@8.18.0) fast-uri: 3.1.0 json-schema-ref-resolver: 3.0.0 rfdc: 1.4.1 @@ -13587,7 +13546,7 @@ snapshots: fastify-plugin@5.1.0: {} - fastify@5.6.1: + fastify@5.7.4: dependencies: '@fastify/ajv-compiler': 4.0.5 '@fastify/error': 4.2.0 @@ -13598,11 +13557,11 @@ snapshots: fast-json-stringify: 6.1.1 find-my-way: 9.3.0 light-my-request: 6.6.0 - pino: 9.14.0 + pino: 10.3.1 process-warning: 5.0.0 rfdc: 1.4.1 secure-json-parse: 4.1.0 - semver: 7.7.2 + semver: 7.7.3 toad-cache: 3.7.0 fastq@1.19.1: @@ -13668,7 +13627,7 @@ snapshots: dependencies: magic-string: 0.30.17 mlly: 1.7.4 - rollup: 4.44.0 + rollup: 4.59.0 flat-cache@4.0.1: dependencies: @@ -13732,6 +13691,8 @@ snapshots: fuse.js@7.1.0: {} + fzf@0.5.2: {} + generate-function@2.3.1: dependencies: is-property: 1.0.2 @@ -13788,14 +13749,7 @@ snapshots: nypm: 0.6.2 pathe: 2.0.3 - git-up@8.1.1: - dependencies: - is-ssh: 1.4.1 - parse-url: 9.2.0 - - git-url-parse@16.1.0: - dependencies: - git-up: 8.1.1 + giget@3.1.2: {} github-from-package@0.0.0: {} @@ -13807,11 +13761,11 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@10.5.0: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 + minimatch: 9.0.8 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -13820,11 +13774,17 @@ snapshots: dependencies: foreground-child: 3.3.1 jackspeak: 4.1.1 - minimatch: 10.1.1 + minimatch: 10.2.4 minipass: 7.1.2 package-json-from-dist: 1.0.1 path-scurry: 2.0.0 + glob@13.0.6: + dependencies: + minimatch: 10.2.4 + minipass: 7.1.3 + path-scurry: 2.0.2 + global-directory@4.0.1: dependencies: ini: 4.1.1 @@ -13838,14 +13798,14 @@ snapshots: define-properties: 1.2.1 gopd: 1.2.0 - globby@15.0.0: + globby@16.1.1: dependencies: '@sindresorhus/merge-streams': 4.0.0 fast-glob: 3.3.3 ignore: 7.0.5 - path-type: 6.0.0 + is-path-inside: 4.0.0 slash: 5.1.0 - unicorn-magic: 0.3.0 + unicorn-magic: 0.4.0 gopd@1.2.0: {} @@ -13857,16 +13817,16 @@ snapshots: dependencies: duplexer: 0.1.2 - h3@1.15.4: + h3@1.15.5: dependencies: cookie-es: 1.2.2 crossws: 0.3.5 defu: 6.1.4 destr: 2.0.5 iron-webcrypto: 1.2.1 - node-mock-http: 1.0.3 + node-mock-http: 1.0.4 radix3: 1.1.2 - ufo: 1.6.1 + ufo: 1.6.3 uncrypto: 0.1.3 happy-dom@20.0.10: @@ -13905,10 +13865,12 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hono@4.10.3: {} + hono@4.12.2: {} hookable@5.5.3: {} + hookable@6.0.1: {} + hosted-git-info@2.8.9: {} html-encoding-sniffer@4.0.0: @@ -13963,6 +13925,7 @@ snapshots: iconv-lite@0.6.3: dependencies: safer-buffer: 2.1.2 + optional: true iconv-lite@0.7.0: dependencies: @@ -14003,9 +13966,9 @@ snapshots: hasown: 2.0.2 side-channel: 1.1.0 - ioredis@5.8.2: + ioredis@5.9.3: dependencies: - '@ioredis/commands': 1.4.0 + '@ioredis/commands': 1.5.0 cluster-key-slot: 1.1.2 debug: 4.4.3 denque: 2.1.0 @@ -14151,10 +14114,6 @@ snapshots: dependencies: call-bound: 1.0.4 - is-ssh@1.4.1: - dependencies: - protocols: 2.0.2 - is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -14263,7 +14222,7 @@ snapshots: js-tokens@9.0.1: {} - js-yaml@4.1.0: + js-yaml@4.1.1: dependencies: argparse: 2.0.1 @@ -14371,7 +14330,7 @@ snapshots: jsonschema: 1.4.1 langium: 3.5.0 langium-railroad: 3.5.0 - lodash: 4.17.21 + lodash: 4.17.23 langium-railroad@3.5.0: dependencies: @@ -14477,14 +14436,14 @@ snapshots: crossws: 0.3.5 defu: 6.1.4 get-port-please: 3.2.0 - h3: 1.15.4 + h3: 1.15.5 http-shutdown: 1.2.2 jiti: 2.6.1 mlly: 1.8.0 - node-forge: 1.3.1 + node-forge: 1.3.3 pathe: 1.1.2 std-env: 3.10.0 - ufo: 1.6.1 + ufo: 1.6.3 untun: 0.1.3 uqr: 0.1.2 @@ -14509,7 +14468,7 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash-es@4.17.21: {} + lodash-es@4.17.23: {} lodash.defaults@4.2.0: {} @@ -14523,7 +14482,7 @@ snapshots: lodash.uniq@4.5.0: {} - lodash@4.17.21: {} + lodash@4.17.23: {} log-symbols@4.1.0: dependencies: @@ -14544,8 +14503,7 @@ snapshots: lru-cache@11.2.2: {} - lru-cache@11.2.6: - optional: true + lru-cache@11.2.6: {} lru-cache@5.1.1: dependencies: @@ -14562,7 +14520,7 @@ snapshots: mlly: 1.8.0 regexp-tree: 0.1.27 type-level-regexp: 0.1.17 - ufo: 1.6.1 + ufo: 1.6.3 unplugin: 2.3.11 magic-string-ast@1.0.3: @@ -14633,8 +14591,6 @@ snapshots: mime@2.6.0: {} - mime@3.0.0: {} - mime@4.1.0: {} mimic-fn@2.1.0: {} @@ -14643,26 +14599,28 @@ snapshots: mimic-response@3.1.0: {} - minimatch@10.1.1: + minimatch@10.2.4: dependencies: - '@isaacs/brace-expansion': 5.0.0 + brace-expansion: 5.0.3 - minimatch@3.1.2: + minimatch@3.1.5: dependencies: brace-expansion: 1.1.12 - minimatch@5.1.6: + minimatch@5.1.9: dependencies: - brace-expansion: 2.0.1 + brace-expansion: 2.0.2 - minimatch@9.0.5: + minimatch@9.0.8: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 5.0.3 minimist@1.2.8: {} minipass@7.1.2: {} + minipass@7.1.3: {} + minizlib@3.1.0: dependencies: minipass: 7.1.2 @@ -14689,7 +14647,7 @@ snapshots: acorn: 8.15.0 pathe: 2.0.3 pkg-types: 1.3.1 - ufo: 1.6.1 + ufo: 1.6.3 mocked-exports@0.1.1: {} @@ -14741,24 +14699,49 @@ snapshots: negotiator@1.0.0: {} - next@16.0.10(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + next@16.1.6(@babel/core@7.28.5)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): + dependencies: + '@next/env': 16.1.6 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001774 + postcss: 8.4.31 + react: 19.2.0 + react-dom: 19.2.0(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 + sharp: 0.34.4 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + next@16.1.6(@babel/core@7.29.0)(react-dom@19.2.0(react@19.2.0))(react@19.2.0): dependencies: - '@next/env': 16.0.10 + '@next/env': 16.1.6 '@swc/helpers': 0.5.15 - caniuse-lite: 1.0.30001761 + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001774 postcss: 8.4.31 react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - styled-jsx: 5.1.6(@babel/core@7.28.5)(react@19.2.0) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.0) optionalDependencies: - '@next/swc-darwin-arm64': 16.0.10 - '@next/swc-darwin-x64': 16.0.10 - '@next/swc-linux-arm64-gnu': 16.0.10 - '@next/swc-linux-arm64-musl': 16.0.10 - '@next/swc-linux-x64-gnu': 16.0.10 - '@next/swc-linux-x64-musl': 16.0.10 - '@next/swc-win32-arm64-msvc': 16.0.10 - '@next/swc-win32-x64-msvc': 16.0.10 + '@next/swc-darwin-arm64': 16.1.6 + '@next/swc-darwin-x64': 16.1.6 + '@next/swc-linux-arm64-gnu': 16.1.6 + '@next/swc-linux-arm64-musl': 16.1.6 + '@next/swc-linux-x64-gnu': 16.1.6 + '@next/swc-linux-x64-musl': 16.1.6 + '@next/swc-win32-arm64-msvc': 16.1.6 + '@next/swc-win32-x64-msvc': 16.1.6 sharp: 0.34.4 transitivePeerDependencies: - '@babel/core' @@ -14766,20 +14749,20 @@ snapshots: nice-try@1.0.5: {} - nitropack@2.12.9(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1): - dependencies: - '@cloudflare/kv-asset-handler': 0.4.0 - '@rollup/plugin-alias': 5.1.1(rollup@4.52.5) - '@rollup/plugin-commonjs': 28.0.9(rollup@4.52.5) - '@rollup/plugin-inject': 5.0.5(rollup@4.52.5) - '@rollup/plugin-json': 6.1.0(rollup@4.52.5) - '@rollup/plugin-node-resolve': 16.0.3(rollup@4.52.5) - '@rollup/plugin-replace': 6.0.3(rollup@4.52.5) - '@rollup/plugin-terser': 0.4.4(rollup@4.52.5) - '@vercel/nft': 0.30.3(rollup@4.52.5) + nitropack@2.13.1(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1): + dependencies: + '@cloudflare/kv-asset-handler': 0.4.2 + '@rollup/plugin-alias': 6.0.0(rollup@4.59.0) + '@rollup/plugin-commonjs': 29.0.0(rollup@4.59.0) + '@rollup/plugin-inject': 5.0.5(rollup@4.59.0) + '@rollup/plugin-json': 6.1.0(rollup@4.59.0) + '@rollup/plugin-node-resolve': 16.0.3(rollup@4.59.0) + '@rollup/plugin-replace': 6.0.3(rollup@4.59.0) + '@rollup/plugin-terser': 0.4.4(rollup@4.59.0) + '@vercel/nft': 1.3.2(rollup@4.59.0) archiver: 7.0.1 c12: 3.3.3(magicast@0.5.1) - chokidar: 4.0.3 + chokidar: 5.0.0 citty: 0.1.6 compatx: 0.2.0 confbox: 0.2.2 @@ -14791,16 +14774,16 @@ snapshots: defu: 6.1.4 destr: 2.0.5 dot-prop: 10.1.0 - esbuild: 0.25.11 + esbuild: 0.27.2 escape-string-regexp: 5.0.0 etag: 1.8.1 exsolve: 1.0.8 - globby: 15.0.0 + globby: 16.1.1 gzip-size: 7.0.0 - h3: 1.15.4 + h3: 1.15.5 hookable: 5.5.3 httpxy: 0.1.7 - ioredis: 5.8.2 + ioredis: 5.9.3 jiti: 2.6.1 klona: 2.0.6 knitwork: 1.3.0 @@ -14810,32 +14793,32 @@ snapshots: mime: 4.1.0 mlly: 1.8.0 node-fetch-native: 1.6.7 - node-mock-http: 1.0.3 + node-mock-http: 1.0.4 ofetch: 1.5.1 ohash: 2.0.11 pathe: 2.0.3 - perfect-debounce: 2.0.0 + perfect-debounce: 2.1.0 pkg-types: 2.3.0 pretty-bytes: 7.1.0 radix3: 1.1.2 - rollup: 4.52.5 - rollup-plugin-visualizer: 6.0.5(rollup@4.52.5) + rollup: 4.59.0 + rollup-plugin-visualizer: 6.0.5(rollup@4.59.0) scule: 1.3.0 - semver: 7.7.3 + semver: 7.7.4 serve-placeholder: 2.0.2 - serve-static: 2.2.0 + serve-static: 2.2.1 source-map: 0.7.6 std-env: 3.10.0 - ufo: 1.6.1 + ufo: 1.6.3 ultrahtml: 1.6.0 uncrypto: 0.1.3 - unctx: 2.4.1 + unctx: 2.5.0 unenv: 2.0.0-rc.24 - unimport: 5.5.0 + unimport: 5.6.0 unplugin-utils: 0.3.1 - unstorage: 1.17.3(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.8.2) + unstorage: 1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3) untyped: 2.0.0 - unwasm: 0.3.11 + unwasm: 0.5.3 youch: 4.1.0-beta.13 youch-core: 0.3.3 transitivePeerDependencies: @@ -14886,11 +14869,11 @@ snapshots: dependencies: whatwg-url: 5.0.0 - node-forge@1.3.1: {} + node-forge@1.3.3: {} node-gyp-build@4.8.4: {} - node-mock-http@1.0.3: {} + node-mock-http@1.0.4: {} node-releases@2.0.27: {} @@ -14913,7 +14896,7 @@ snapshots: chalk: 2.4.2 cross-spawn: 6.0.6 memorystream: 0.3.1 - minimatch: 3.1.2 + minimatch: 3.1.5 pidtree: 0.3.1 read-pkg: 3.0.0 shell-quote: 1.8.3 @@ -14932,141 +14915,18 @@ snapshots: dependencies: boolbase: 1.0.0 - nuxt@4.2.2(6ed21839bfb37f6518a04a21db8dd1a5): - dependencies: - '@dxup/nuxt': 0.2.2(magicast@0.5.1) - '@nuxt/cli': 3.31.3(cac@6.7.14)(magicast@0.5.1) - '@nuxt/devtools': 3.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@nuxt/nitro-server': 4.2.2(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.8.2)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.2.2(6ed21839bfb37f6518a04a21db8dd1a5))(typescript@5.9.3) - '@nuxt/schema': 4.2.2 - '@nuxt/telemetry': 2.6.6(magicast@0.5.1) - '@nuxt/vite-builder': 4.2.2(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.2.2(6ed21839bfb37f6518a04a21db8dd1a5))(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2) - '@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) - '@vue/shared': 3.5.26 - c12: 3.3.3(magicast@0.5.1) - chokidar: 5.0.0 - compatx: 0.2.0 - consola: 3.4.2 - cookie-es: 2.0.0 - defu: 6.1.4 - destr: 2.0.5 - devalue: 5.6.1 - errx: 0.1.0 - escape-string-regexp: 5.0.0 - exsolve: 1.0.8 - h3: 1.15.4 - hookable: 5.5.3 - ignore: 7.0.5 - impound: 1.0.0 - jiti: 2.6.1 - klona: 2.0.6 - knitwork: 1.3.0 - magic-string: 0.30.21 - mlly: 1.8.0 - nanotar: 0.2.0 - nypm: 0.6.2 - ofetch: 1.5.1 - ohash: 2.0.11 - on-change: 6.0.1 - oxc-minify: 0.102.0 - oxc-parser: 0.102.0 - oxc-transform: 0.102.0 - oxc-walker: 0.6.0(oxc-parser@0.102.0) - pathe: 2.0.3 - perfect-debounce: 2.0.0 - pkg-types: 2.3.0 - radix3: 1.1.2 - scule: 1.3.0 - semver: 7.7.3 - std-env: 3.10.0 - tinyglobby: 0.2.15 - ufo: 1.6.1 - ultrahtml: 1.6.0 - uncrypto: 0.1.3 - unctx: 2.4.1 - unimport: 5.5.0 - unplugin: 2.3.11 - unplugin-vue-router: 0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)) - untyped: 2.0.0 - vue: 3.5.26(typescript@5.9.3) - vue-router: 4.6.4(vue@3.5.26(typescript@5.9.3)) - optionalDependencies: - '@parcel/watcher': 2.5.1 - '@types/node': 20.19.24 - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@biomejs/biome' - - '@capacitor/preferences' - - '@deno/kv' - - '@electric-sql/pglite' - - '@libsql/client' - - '@netlify/blobs' - - '@planetscale/database' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - '@vitejs/devtools' - - '@vue/compiler-sfc' - - aws4fetch - - bare-abort-controller - - better-sqlite3 - - bufferutil - - cac - - commander - - db0 - - drizzle-orm - - encoding - - eslint - - idb-keyval - - ioredis - - less - - lightningcss - - magicast - - meow - - mysql2 - - optionator - - oxlint - - react-native-b4a - - rolldown - - rollup - - sass - - sass-embedded - - sqlite3 - - stylelint - - stylus - - sugarss - - supports-color - - terser - - tsx - - typescript - - uploadthing - - utf-8-validate - - vite - - vls - - vti - - vue-tsc - - xml2js - - yaml - - nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0): - dependencies: - '@dxup/nuxt': 0.2.2(magicast@0.5.1) - '@nuxt/cli': 3.31.3(cac@6.7.14)(magicast@0.5.1) - '@nuxt/devtools': 3.1.1(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)) - '@nuxt/kit': 4.2.2(magicast@0.5.1) - '@nuxt/nitro-server': 4.2.2(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.8.2)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(typescript@5.9.3) - '@nuxt/schema': 4.2.2 - '@nuxt/telemetry': 2.6.6(magicast@0.5.1) - '@nuxt/vite-builder': 4.2.2(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.2.2(8d53318c89c53efd506d8dd328c0edc0))(optionator@0.9.4)(rollup@4.52.5)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.26(typescript@5.9.3))(yaml@2.8.2) - '@unhead/vue': 2.0.19(vue@3.5.26(typescript@5.9.3)) - '@vue/shared': 3.5.26 + nuxt@4.3.1(7e2836fb0faa0d6f7376f741b393f215): + dependencies: + '@dxup/nuxt': 0.3.2(magicast@0.5.1) + '@nuxt/cli': 3.33.1(@nuxt/schema@4.3.1)(cac@6.7.14)(magicast@0.5.1) + '@nuxt/devtools': 3.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)) + '@nuxt/kit': 4.3.1(magicast@0.5.1) + '@nuxt/nitro-server': 4.3.1(better-sqlite3@12.5.0)(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(ioredis@5.9.3)(magicast@0.5.1)(mysql2@3.16.1)(nuxt@4.3.1(7e2836fb0faa0d6f7376f741b393f215))(typescript@5.9.3) + '@nuxt/schema': 4.3.1 + '@nuxt/telemetry': 2.7.0(@nuxt/kit@4.3.1(magicast@0.5.1)) + '@nuxt/vite-builder': 4.3.1(@types/node@20.19.24)(eslint@9.29.0(jiti@2.6.1))(lightningcss@1.30.2)(magicast@0.5.1)(nuxt@4.3.1(7e2836fb0faa0d6f7376f741b393f215))(optionator@0.9.4)(rollup@4.59.0)(terser@5.44.0)(tsx@4.20.3)(typescript@5.9.3)(vue-tsc@3.2.5(typescript@5.9.3))(vue@3.5.29(typescript@5.9.3))(yaml@2.8.2) + '@unhead/vue': 2.1.7(vue@3.5.29(typescript@5.9.3)) + '@vue/shared': 3.5.29 c12: 3.3.3(magicast@0.5.1) chokidar: 5.0.0 compatx: 0.2.0 @@ -15074,11 +14934,11 @@ snapshots: cookie-es: 2.0.0 defu: 6.1.4 destr: 2.0.5 - devalue: 5.6.1 + devalue: 5.6.3 errx: 0.1.0 escape-string-regexp: 5.0.0 exsolve: 1.0.8 - h3: 1.15.4 + h3: 1.15.5 hookable: 5.5.3 ignore: 7.0.5 impound: 1.0.0 @@ -15088,32 +14948,32 @@ snapshots: magic-string: 0.30.21 mlly: 1.8.0 nanotar: 0.2.0 - nypm: 0.6.2 + nypm: 0.6.5 ofetch: 1.5.1 ohash: 2.0.11 - on-change: 6.0.1 - oxc-minify: 0.102.0 - oxc-parser: 0.102.0 - oxc-transform: 0.102.0 - oxc-walker: 0.6.0(oxc-parser@0.102.0) + on-change: 6.0.2 + oxc-minify: 0.112.0 + oxc-parser: 0.112.0 + oxc-transform: 0.112.0 + oxc-walker: 0.7.0(oxc-parser@0.112.0) pathe: 2.0.3 - perfect-debounce: 2.0.0 + perfect-debounce: 2.1.0 pkg-types: 2.3.0 - radix3: 1.1.2 + rou3: 0.7.12 scule: 1.3.0 - semver: 7.7.3 + semver: 7.7.4 std-env: 3.10.0 tinyglobby: 0.2.15 - ufo: 1.6.1 + ufo: 1.6.3 ultrahtml: 1.6.0 uncrypto: 0.1.3 - unctx: 2.4.1 - unimport: 5.5.0 - unplugin: 2.3.11 - unplugin-vue-router: 0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.22(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)) + unctx: 2.5.0 + unimport: 5.6.0 + unplugin: 3.0.0 + unplugin-vue-router: 0.19.2(@vue/compiler-sfc@3.5.29)(vue-router@4.6.4(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)) untyped: 2.0.0 - vue: 3.5.26(typescript@5.9.3) - vue-router: 4.6.4(vue@3.5.26(typescript@5.9.3)) + vue: 3.5.29(typescript@5.9.3) + vue-router: 4.6.4(vue@3.5.29(typescript@5.9.3)) optionalDependencies: '@parcel/watcher': 2.5.1 '@types/node': 20.19.24 @@ -15186,6 +15046,12 @@ snapshots: pkg-types: 2.3.0 tinyexec: 1.0.1 + nypm@0.6.5: + dependencies: + citty: 0.2.1 + pathe: 2.0.3 + tinyexec: 1.0.2 + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -15236,11 +15102,13 @@ snapshots: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 - ufo: 1.6.1 + ufo: 1.6.3 + + ofetch@2.0.0-alpha.3: {} ohash@2.0.11: {} - on-change@6.0.1: {} + on-change@6.0.2: {} on-exit-leak-free@2.1.2: {} @@ -15304,66 +15172,81 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 - oxc-minify@0.102.0: + oxc-minify@0.112.0: optionalDependencies: - '@oxc-minify/binding-android-arm64': 0.102.0 - '@oxc-minify/binding-darwin-arm64': 0.102.0 - '@oxc-minify/binding-darwin-x64': 0.102.0 - '@oxc-minify/binding-freebsd-x64': 0.102.0 - '@oxc-minify/binding-linux-arm-gnueabihf': 0.102.0 - '@oxc-minify/binding-linux-arm64-gnu': 0.102.0 - '@oxc-minify/binding-linux-arm64-musl': 0.102.0 - '@oxc-minify/binding-linux-riscv64-gnu': 0.102.0 - '@oxc-minify/binding-linux-s390x-gnu': 0.102.0 - '@oxc-minify/binding-linux-x64-gnu': 0.102.0 - '@oxc-minify/binding-linux-x64-musl': 0.102.0 - '@oxc-minify/binding-openharmony-arm64': 0.102.0 - '@oxc-minify/binding-wasm32-wasi': 0.102.0 - '@oxc-minify/binding-win32-arm64-msvc': 0.102.0 - '@oxc-minify/binding-win32-x64-msvc': 0.102.0 - - oxc-parser@0.102.0: - dependencies: - '@oxc-project/types': 0.102.0 + '@oxc-minify/binding-android-arm-eabi': 0.112.0 + '@oxc-minify/binding-android-arm64': 0.112.0 + '@oxc-minify/binding-darwin-arm64': 0.112.0 + '@oxc-minify/binding-darwin-x64': 0.112.0 + '@oxc-minify/binding-freebsd-x64': 0.112.0 + '@oxc-minify/binding-linux-arm-gnueabihf': 0.112.0 + '@oxc-minify/binding-linux-arm-musleabihf': 0.112.0 + '@oxc-minify/binding-linux-arm64-gnu': 0.112.0 + '@oxc-minify/binding-linux-arm64-musl': 0.112.0 + '@oxc-minify/binding-linux-ppc64-gnu': 0.112.0 + '@oxc-minify/binding-linux-riscv64-gnu': 0.112.0 + '@oxc-minify/binding-linux-riscv64-musl': 0.112.0 + '@oxc-minify/binding-linux-s390x-gnu': 0.112.0 + '@oxc-minify/binding-linux-x64-gnu': 0.112.0 + '@oxc-minify/binding-linux-x64-musl': 0.112.0 + '@oxc-minify/binding-openharmony-arm64': 0.112.0 + '@oxc-minify/binding-wasm32-wasi': 0.112.0 + '@oxc-minify/binding-win32-arm64-msvc': 0.112.0 + '@oxc-minify/binding-win32-ia32-msvc': 0.112.0 + '@oxc-minify/binding-win32-x64-msvc': 0.112.0 + + oxc-parser@0.112.0: + dependencies: + '@oxc-project/types': 0.112.0 optionalDependencies: - '@oxc-parser/binding-android-arm64': 0.102.0 - '@oxc-parser/binding-darwin-arm64': 0.102.0 - '@oxc-parser/binding-darwin-x64': 0.102.0 - '@oxc-parser/binding-freebsd-x64': 0.102.0 - '@oxc-parser/binding-linux-arm-gnueabihf': 0.102.0 - '@oxc-parser/binding-linux-arm64-gnu': 0.102.0 - '@oxc-parser/binding-linux-arm64-musl': 0.102.0 - '@oxc-parser/binding-linux-riscv64-gnu': 0.102.0 - '@oxc-parser/binding-linux-s390x-gnu': 0.102.0 - '@oxc-parser/binding-linux-x64-gnu': 0.102.0 - '@oxc-parser/binding-linux-x64-musl': 0.102.0 - '@oxc-parser/binding-openharmony-arm64': 0.102.0 - '@oxc-parser/binding-wasm32-wasi': 0.102.0 - '@oxc-parser/binding-win32-arm64-msvc': 0.102.0 - '@oxc-parser/binding-win32-x64-msvc': 0.102.0 - - oxc-transform@0.102.0: + '@oxc-parser/binding-android-arm-eabi': 0.112.0 + '@oxc-parser/binding-android-arm64': 0.112.0 + '@oxc-parser/binding-darwin-arm64': 0.112.0 + '@oxc-parser/binding-darwin-x64': 0.112.0 + '@oxc-parser/binding-freebsd-x64': 0.112.0 + '@oxc-parser/binding-linux-arm-gnueabihf': 0.112.0 + '@oxc-parser/binding-linux-arm-musleabihf': 0.112.0 + '@oxc-parser/binding-linux-arm64-gnu': 0.112.0 + '@oxc-parser/binding-linux-arm64-musl': 0.112.0 + '@oxc-parser/binding-linux-ppc64-gnu': 0.112.0 + '@oxc-parser/binding-linux-riscv64-gnu': 0.112.0 + '@oxc-parser/binding-linux-riscv64-musl': 0.112.0 + '@oxc-parser/binding-linux-s390x-gnu': 0.112.0 + '@oxc-parser/binding-linux-x64-gnu': 0.112.0 + '@oxc-parser/binding-linux-x64-musl': 0.112.0 + '@oxc-parser/binding-openharmony-arm64': 0.112.0 + '@oxc-parser/binding-wasm32-wasi': 0.112.0 + '@oxc-parser/binding-win32-arm64-msvc': 0.112.0 + '@oxc-parser/binding-win32-ia32-msvc': 0.112.0 + '@oxc-parser/binding-win32-x64-msvc': 0.112.0 + + oxc-transform@0.112.0: optionalDependencies: - '@oxc-transform/binding-android-arm64': 0.102.0 - '@oxc-transform/binding-darwin-arm64': 0.102.0 - '@oxc-transform/binding-darwin-x64': 0.102.0 - '@oxc-transform/binding-freebsd-x64': 0.102.0 - '@oxc-transform/binding-linux-arm-gnueabihf': 0.102.0 - '@oxc-transform/binding-linux-arm64-gnu': 0.102.0 - '@oxc-transform/binding-linux-arm64-musl': 0.102.0 - '@oxc-transform/binding-linux-riscv64-gnu': 0.102.0 - '@oxc-transform/binding-linux-s390x-gnu': 0.102.0 - '@oxc-transform/binding-linux-x64-gnu': 0.102.0 - '@oxc-transform/binding-linux-x64-musl': 0.102.0 - '@oxc-transform/binding-openharmony-arm64': 0.102.0 - '@oxc-transform/binding-wasm32-wasi': 0.102.0 - '@oxc-transform/binding-win32-arm64-msvc': 0.102.0 - '@oxc-transform/binding-win32-x64-msvc': 0.102.0 - - oxc-walker@0.6.0(oxc-parser@0.102.0): + '@oxc-transform/binding-android-arm-eabi': 0.112.0 + '@oxc-transform/binding-android-arm64': 0.112.0 + '@oxc-transform/binding-darwin-arm64': 0.112.0 + '@oxc-transform/binding-darwin-x64': 0.112.0 + '@oxc-transform/binding-freebsd-x64': 0.112.0 + '@oxc-transform/binding-linux-arm-gnueabihf': 0.112.0 + '@oxc-transform/binding-linux-arm-musleabihf': 0.112.0 + '@oxc-transform/binding-linux-arm64-gnu': 0.112.0 + '@oxc-transform/binding-linux-arm64-musl': 0.112.0 + '@oxc-transform/binding-linux-ppc64-gnu': 0.112.0 + '@oxc-transform/binding-linux-riscv64-gnu': 0.112.0 + '@oxc-transform/binding-linux-riscv64-musl': 0.112.0 + '@oxc-transform/binding-linux-s390x-gnu': 0.112.0 + '@oxc-transform/binding-linux-x64-gnu': 0.112.0 + '@oxc-transform/binding-linux-x64-musl': 0.112.0 + '@oxc-transform/binding-openharmony-arm64': 0.112.0 + '@oxc-transform/binding-wasm32-wasi': 0.112.0 + '@oxc-transform/binding-win32-arm64-msvc': 0.112.0 + '@oxc-transform/binding-win32-ia32-msvc': 0.112.0 + '@oxc-transform/binding-win32-x64-msvc': 0.112.0 + + oxc-walker@0.7.0(oxc-parser@0.112.0): dependencies: magic-regexp: 0.10.0 - oxc-parser: 0.102.0 + oxc-parser: 0.112.0 p-limit@3.1.0: dependencies: @@ -15388,15 +15271,6 @@ snapshots: parse-ms@4.0.0: {} - parse-path@7.1.0: - dependencies: - protocols: 2.0.2 - - parse-url@9.2.0: - dependencies: - '@types/parse-path': 7.1.0 - parse-path: 7.1.0 - parse5@8.0.0: dependencies: entities: 6.0.1 @@ -15426,14 +15300,17 @@ snapshots: lru-cache: 11.2.2 minipass: 7.1.2 + path-scurry@2.0.2: + dependencies: + lru-cache: 11.2.6 + minipass: 7.1.3 + path-to-regexp@8.3.0: {} path-type@3.0.0: dependencies: pify: 3.0.0 - path-type@6.0.0: {} - pathe@1.1.2: {} pathe@2.0.3: {} @@ -15442,6 +15319,8 @@ snapshots: perfect-debounce@2.0.0: {} + perfect-debounce@2.1.0: {} + pg-cloudflare@1.2.7: optional: true @@ -15501,25 +15380,25 @@ snapshots: pify@3.0.0: {} - pino-abstract-transport@2.0.0: + pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 pino-std-serializers@7.0.0: {} - pino@9.14.0: + pino@10.3.1: dependencies: '@pinojs/redact': 0.4.0 atomic-sleep: 1.0.0 on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 + pino-abstract-transport: 3.0.0 pino-std-serializers: 7.0.0 process-warning: 5.0.0 quick-format-unescaped: 4.0.4 real-require: 0.2.0 safe-stable-stringify: 2.5.0 sonic-boom: 4.2.0 - thread-stream: 3.1.0 + thread-stream: 4.0.0 pirates@4.0.7: {} @@ -15750,7 +15629,7 @@ snapshots: pump: 3.0.2 rc: 1.2.8 simple-get: 4.0.1 - tar-fs: 2.1.2 + tar-fs: 2.1.4 tunnel-agent: 0.6.0 prelude-ls@1.2.1: {} @@ -15815,8 +15694,6 @@ snapshots: propagate@2.0.1: {} - protocols@2.0.2: {} - proxy-addr@2.0.7: dependencies: forwarded: 0.2.0 @@ -15831,11 +15708,7 @@ snapshots: pure-rand@6.1.0: {} - qs@6.13.0: - dependencies: - side-channel: 1.1.0 - - qs@6.14.0: + qs@6.15.0: dependencies: side-channel: 1.1.0 @@ -15867,6 +15740,11 @@ snapshots: defu: 6.1.4 destr: 2.0.5 + rc9@3.0.0: + dependencies: + defu: 6.1.4 + destr: 2.0.5 + rc@1.2.8: dependencies: deep-extend: 0.6.0 @@ -15917,7 +15795,7 @@ snapshots: readdir-glob@1.1.3: dependencies: - minimatch: 5.1.6 + minimatch: 5.1.9 readdirp@4.1.2: {} @@ -15990,67 +15868,44 @@ snapshots: rfdc@1.4.1: {} - rollup-plugin-visualizer@6.0.5(rollup@4.52.5): + rollup-plugin-visualizer@6.0.5(rollup@4.59.0): dependencies: open: 8.4.2 picomatch: 4.0.3 source-map: 0.7.6 yargs: 17.7.2 optionalDependencies: - rollup: 4.52.5 - - rollup@4.44.0: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.44.0 - '@rollup/rollup-android-arm64': 4.44.0 - '@rollup/rollup-darwin-arm64': 4.44.0 - '@rollup/rollup-darwin-x64': 4.44.0 - '@rollup/rollup-freebsd-arm64': 4.44.0 - '@rollup/rollup-freebsd-x64': 4.44.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.44.0 - '@rollup/rollup-linux-arm-musleabihf': 4.44.0 - '@rollup/rollup-linux-arm64-gnu': 4.44.0 - '@rollup/rollup-linux-arm64-musl': 4.44.0 - '@rollup/rollup-linux-loongarch64-gnu': 4.44.0 - '@rollup/rollup-linux-powerpc64le-gnu': 4.44.0 - '@rollup/rollup-linux-riscv64-gnu': 4.44.0 - '@rollup/rollup-linux-riscv64-musl': 4.44.0 - '@rollup/rollup-linux-s390x-gnu': 4.44.0 - '@rollup/rollup-linux-x64-gnu': 4.44.0 - '@rollup/rollup-linux-x64-musl': 4.44.0 - '@rollup/rollup-win32-arm64-msvc': 4.44.0 - '@rollup/rollup-win32-ia32-msvc': 4.44.0 - '@rollup/rollup-win32-x64-msvc': 4.44.0 - fsevents: 2.3.3 + rollup: 4.59.0 - rollup@4.52.5: + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.52.5 - '@rollup/rollup-android-arm64': 4.52.5 - '@rollup/rollup-darwin-arm64': 4.52.5 - '@rollup/rollup-darwin-x64': 4.52.5 - '@rollup/rollup-freebsd-arm64': 4.52.5 - '@rollup/rollup-freebsd-x64': 4.52.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.52.5 - '@rollup/rollup-linux-arm-musleabihf': 4.52.5 - '@rollup/rollup-linux-arm64-gnu': 4.52.5 - '@rollup/rollup-linux-arm64-musl': 4.52.5 - '@rollup/rollup-linux-loong64-gnu': 4.52.5 - '@rollup/rollup-linux-ppc64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-gnu': 4.52.5 - '@rollup/rollup-linux-riscv64-musl': 4.52.5 - '@rollup/rollup-linux-s390x-gnu': 4.52.5 - '@rollup/rollup-linux-x64-gnu': 4.52.5 - '@rollup/rollup-linux-x64-musl': 4.52.5 - '@rollup/rollup-openharmony-arm64': 4.52.5 - '@rollup/rollup-win32-arm64-msvc': 4.52.5 - '@rollup/rollup-win32-ia32-msvc': 4.52.5 - '@rollup/rollup-win32-x64-gnu': 4.52.5 - '@rollup/rollup-win32-x64-msvc': 4.52.5 + '@rollup/rollup-android-arm-eabi': 4.59.0 + '@rollup/rollup-android-arm64': 4.59.0 + '@rollup/rollup-darwin-arm64': 4.59.0 + '@rollup/rollup-darwin-x64': 4.59.0 + '@rollup/rollup-freebsd-arm64': 4.59.0 + '@rollup/rollup-freebsd-x64': 4.59.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.59.0 + '@rollup/rollup-linux-arm-musleabihf': 4.59.0 + '@rollup/rollup-linux-arm64-gnu': 4.59.0 + '@rollup/rollup-linux-arm64-musl': 4.59.0 + '@rollup/rollup-linux-loong64-gnu': 4.59.0 + '@rollup/rollup-linux-loong64-musl': 4.59.0 + '@rollup/rollup-linux-ppc64-gnu': 4.59.0 + '@rollup/rollup-linux-ppc64-musl': 4.59.0 + '@rollup/rollup-linux-riscv64-gnu': 4.59.0 + '@rollup/rollup-linux-riscv64-musl': 4.59.0 + '@rollup/rollup-linux-s390x-gnu': 4.59.0 + '@rollup/rollup-linux-x64-gnu': 4.59.0 + '@rollup/rollup-linux-x64-musl': 4.59.0 + '@rollup/rollup-openbsd-x64': 4.59.0 + '@rollup/rollup-openharmony-arm64': 4.59.0 + '@rollup/rollup-win32-arm64-msvc': 4.59.0 + '@rollup/rollup-win32-ia32-msvc': 4.59.0 + '@rollup/rollup-win32-x64-gnu': 4.59.0 + '@rollup/rollup-win32-x64-msvc': 4.59.0 fsevents: 2.3.3 rou3@0.7.12: {} @@ -16127,6 +15982,8 @@ snapshots: semver@7.7.3: {} + semver@7.7.4: {} + send@1.2.0: dependencies: debug: 4.4.1 @@ -16149,7 +16006,7 @@ snapshots: dependencies: randombytes: 2.1.0 - seroval@1.4.1: {} + seroval@1.5.0: {} serve-placeholder@2.0.2: dependencies: @@ -16164,8 +16021,19 @@ snapshots: transitivePeerDependencies: - supports-color + serve-static@2.2.1: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 1.2.0 + transitivePeerDependencies: + - supports-color + set-cookie-parser@2.7.2: {} + set-cookie-parser@3.0.1: {} + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -16194,7 +16062,7 @@ snapshots: dependencies: '@img/colour': 1.0.0 detect-libc: 2.1.2 - semver: 7.7.3 + semver: 7.7.4 optionalDependencies: '@img/sharp-darwin-arm64': 0.34.4 '@img/sharp-darwin-x64': 0.34.4 @@ -16337,7 +16205,7 @@ snapshots: sqlstring@2.3.3: {} - srvx@0.9.8: {} + srvx@0.11.8: {} stable-hash@0.0.5: {} @@ -16477,6 +16345,13 @@ snapshots: optionalDependencies: '@babel/core': 7.28.5 + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.0): + dependencies: + client-only: 0.0.1 + react: 19.2.0 + optionalDependencies: + '@babel/core': 7.29.0 + stylehacks@7.0.7(postcss@8.5.6): dependencies: browserslist: 4.28.1 @@ -16487,7 +16362,7 @@ snapshots: dependencies: '@jridgewell/gen-mapping': 0.3.8 commander: 4.1.1 - glob: 10.4.5 + glob: 10.5.0 lines-and-columns: 1.2.4 mz: 2.7.0 pirates: 4.0.7 @@ -16503,7 +16378,7 @@ snapshots: formidable: 3.5.4 methods: 1.1.2 mime: 2.6.0 - qs: 6.13.0 + qs: 6.15.0 transitivePeerDependencies: - supports-color @@ -16530,54 +16405,37 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.45.6)(typescript@5.9.3): + svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3): dependencies: '@jridgewell/trace-mapping': 0.3.31 chokidar: 4.0.3 fdir: 6.5.0(picomatch@4.0.3) picocolors: 1.1.1 sade: 1.8.1 - svelte: 5.45.6 + svelte: 5.53.5 typescript: 5.9.3 transitivePeerDependencies: - picomatch - svelte2tsx@0.7.46(svelte@5.45.6)(typescript@5.9.3): + svelte2tsx@0.7.46(svelte@5.53.5)(typescript@5.9.3): dependencies: dedent-js: 1.0.1 scule: 1.3.0 - svelte: 5.45.6 + svelte: 5.53.5 typescript: 5.9.3 - svelte@5.45.6: - dependencies: - '@jridgewell/remapping': 2.3.5 - '@jridgewell/sourcemap-codec': 1.5.5 - '@sveltejs/acorn-typescript': 1.0.6(acorn@8.15.0) - '@types/estree': 1.0.8 - acorn: 8.15.0 - aria-query: 5.3.2 - axobject-query: 4.1.0 - clsx: 2.1.1 - devalue: 5.6.1 - esm-env: 1.2.2 - esrap: 2.2.1 - is-reference: 3.0.3 - locate-character: 3.0.0 - magic-string: 0.30.21 - zimmerframe: 1.1.4 - - svelte@5.46.1: + svelte@5.53.5: dependencies: '@jridgewell/remapping': 2.3.5 '@jridgewell/sourcemap-codec': 1.5.5 '@sveltejs/acorn-typescript': 1.0.9(acorn@8.15.0) '@types/estree': 1.0.8 + '@types/trusted-types': 2.0.7 acorn: 8.15.0 - aria-query: 5.3.2 + aria-query: 5.3.1 axobject-query: 4.1.0 clsx: 2.1.1 - devalue: 5.6.2 + devalue: 5.6.3 esm-env: 1.2.2 esrap: 2.2.3 is-reference: 3.0.3 @@ -16608,7 +16466,7 @@ snapshots: tapable@2.3.0: {} - tar-fs@2.1.2: + tar-fs@2.1.4: dependencies: chownr: 1.1.4 mkdirp-classic: 0.5.3 @@ -16632,7 +16490,7 @@ snapshots: - bare-abort-controller - react-native-b4a - tar@7.5.1: + tar@7.5.9: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -16661,7 +16519,7 @@ snapshots: dependencies: any-promise: 1.3.0 - thread-stream@3.1.0: + thread-stream@4.0.0: dependencies: real-require: 0.2.0 @@ -16695,7 +16553,7 @@ snapshots: tldts-core: 7.0.23 optional: true - tmp@0.2.3: {} + tmp@0.2.5: {} to-regex-range@5.0.1: dependencies: @@ -16765,7 +16623,7 @@ snapshots: picocolors: 1.1.1 postcss-load-config: 6.0.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.20.3)(yaml@2.8.0) resolve-from: 5.0.0 - rollup: 4.44.0 + rollup: 4.59.0 source-map: 0.8.0-beta.0 sucrase: 3.35.0 tinyexec: 0.3.2 @@ -16893,6 +16751,8 @@ snapshots: ufo@1.6.1: {} + ufo@1.6.3: {} + uint8array-extras@1.5.0: {} ulid@3.0.0: {} @@ -16908,7 +16768,7 @@ snapshots: uncrypto@0.1.3: {} - unctx@2.4.1: + unctx@2.5.0: dependencies: acorn: 8.15.0 estree-walker: 3.0.3 @@ -16923,13 +16783,15 @@ snapshots: dependencies: pathe: 2.0.3 - unhead@2.0.19: + unhead@2.1.7: dependencies: - hookable: 5.5.3 + hookable: 6.0.1 unicorn-magic@0.3.0: {} - unimport@5.5.0: + unicorn-magic@0.4.0: {} + + unimport@5.6.0: dependencies: acorn: 8.15.0 escape-string-regexp: 5.0.0 @@ -16960,12 +16822,12 @@ snapshots: pathe: 2.0.3 picomatch: 4.0.3 - unplugin-vue-router@0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.22(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)): + unplugin-vue-router@0.19.2(@vue/compiler-sfc@3.5.29)(vue-router@4.6.4(vue@3.5.29(typescript@5.9.3)))(vue@3.5.29(typescript@5.9.3)): dependencies: - '@babel/generator': 7.28.5 - '@vue-macros/common': 3.1.1(vue@3.5.26(typescript@5.9.3)) - '@vue/compiler-sfc': 3.5.26 - '@vue/language-core': 3.2.1 + '@babel/generator': 7.28.6 + '@vue-macros/common': 3.1.1(vue@3.5.29(typescript@5.9.3)) + '@vue/compiler-sfc': 3.5.29 + '@vue/language-core': 3.2.5 ast-walker-scope: 0.8.3 chokidar: 5.0.0 json5: 2.2.3 @@ -16981,39 +16843,20 @@ snapshots: unplugin-utils: 0.3.1 yaml: 2.8.2 optionalDependencies: - vue-router: 4.6.4(vue@3.5.22(typescript@5.9.3)) + vue-router: 4.6.4(vue@3.5.29(typescript@5.9.3)) transitivePeerDependencies: - vue - unplugin-vue-router@0.19.1(@vue/compiler-sfc@3.5.26)(vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)))(vue@3.5.26(typescript@5.9.3)): + unplugin@2.3.11: dependencies: - '@babel/generator': 7.28.5 - '@vue-macros/common': 3.1.1(vue@3.5.26(typescript@5.9.3)) - '@vue/compiler-sfc': 3.5.26 - '@vue/language-core': 3.2.1 - ast-walker-scope: 0.8.3 - chokidar: 5.0.0 - json5: 2.2.3 - local-pkg: 1.1.2 - magic-string: 0.30.21 - mlly: 1.8.0 - muggle-string: 0.4.1 - pathe: 2.0.3 + '@jridgewell/remapping': 2.3.5 + acorn: 8.15.0 picomatch: 4.0.3 - scule: 1.3.0 - tinyglobby: 0.2.15 - unplugin: 2.3.11 - unplugin-utils: 0.3.1 - yaml: 2.8.2 - optionalDependencies: - vue-router: 4.6.4(vue@3.5.26(typescript@5.9.3)) - transitivePeerDependencies: - - vue + webpack-virtual-modules: 0.6.2 - unplugin@2.3.11: + unplugin@3.0.0: dependencies: '@jridgewell/remapping': 2.3.5 - acorn: 8.15.0 picomatch: 4.0.3 webpack-virtual-modules: 0.6.2 @@ -17041,19 +16884,19 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - unstorage@1.17.3(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.8.2): + unstorage@1.17.4(db0@0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1))(ioredis@5.9.3): dependencies: anymatch: 3.1.3 - chokidar: 4.0.3 + chokidar: 5.0.0 destr: 2.0.5 - h3: 1.15.4 - lru-cache: 10.4.3 + h3: 1.15.5 + lru-cache: 11.2.6 node-fetch-native: 1.6.7 ofetch: 1.5.1 - ufo: 1.6.1 + ufo: 1.6.3 optionalDependencies: db0: 0.3.4(better-sqlite3@12.5.0)(drizzle-orm@0.41.0(@prisma/client@5.22.0(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3)))(@types/better-sqlite3@7.6.13)(@types/pg@8.16.0)(@types/sql.js@1.4.9)(better-sqlite3@12.5.0)(bun-types@1.3.3)(kysely@0.28.8)(mysql2@3.16.1)(pg@8.16.3)(prisma@6.19.0(magicast@0.5.1)(typescript@5.9.3))(sql.js@1.13.0))(mysql2@3.16.1) - ioredis: 5.8.2 + ioredis: 5.9.3 untun@0.1.3: dependencies: @@ -17069,14 +16912,14 @@ snapshots: knitwork: 1.3.0 scule: 1.3.0 - unwasm@0.3.11: + unwasm@0.5.3: dependencies: + exsolve: 1.0.8 knitwork: 1.3.0 magic-string: 0.30.21 mlly: 1.8.0 pathe: 2.0.3 pkg-types: 2.3.0 - unplugin: 2.3.11 update-browserslist-db@1.1.4(browserslist@4.27.0): dependencies: @@ -17109,23 +16952,23 @@ snapshots: vary@1.1.2: {} - vite-dev-rpc@1.1.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): + vite-dev-rpc@1.1.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): dependencies: birpc: 2.9.0 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-hot-client: 2.1.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-hot-client: 2.1.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) - vite-hot-client@2.1.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): + vite-hot-client@2.1.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): dependencies: - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-node@5.2.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2): + vite-node@5.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2): dependencies: cac: 6.7.14 - es-module-lexer: 1.7.0 + es-module-lexer: 2.0.0 obug: 2.1.1 pathe: 2.0.3 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) transitivePeerDependencies: - '@types/node' - jiti @@ -17139,16 +16982,16 @@ snapshots: - tsx - yaml - vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)): + vite-plugin-checker@0.12.0(eslint@9.29.0(jiti@2.6.1))(optionator@0.9.4)(typescript@5.9.3)(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue-tsc@3.2.5(typescript@5.9.3)): dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.28.6 chokidar: 4.0.3 npm-run-path: 6.0.0 picocolors: 1.1.1 picomatch: 4.0.3 tiny-invariant: 1.3.3 tinyglobby: 0.2.15 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) vscode-uri: 3.1.0 optionalDependencies: eslint: 9.29.0(jiti@2.6.1) @@ -17156,32 +16999,32 @@ snapshots: typescript: 5.9.3 vue-tsc: 3.2.5(typescript@5.9.3) - vite-plugin-inspect@11.3.3(@nuxt/kit@4.2.2(magicast@0.5.1))(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): + vite-plugin-inspect@11.3.3(@nuxt/kit@4.3.1(magicast@0.5.1))(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): dependencies: ansis: 4.2.0 debug: 4.4.3 error-stack-parser-es: 1.0.5 ohash: 2.0.11 open: 10.2.0 - perfect-debounce: 2.0.0 + perfect-debounce: 2.1.0 sirv: 3.0.2 unplugin-utils: 0.3.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vite-dev-rpc: 1.1.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vite-dev-rpc: 1.1.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)) optionalDependencies: - '@nuxt/kit': 4.2.2(magicast@0.5.1) + '@nuxt/kit': 4.3.1(magicast@0.5.1) transitivePeerDependencies: - supports-color - vite-plugin-vue-tracer@1.2.0(vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.26(typescript@5.9.3)): + vite-plugin-vue-tracer@1.2.0(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2))(vue@3.5.29(typescript@5.9.3)): dependencies: estree-walker: 3.0.3 exsolve: 1.0.8 magic-string: 0.30.21 pathe: 2.0.3 source-map-js: 1.2.1 - vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) - vue: 3.5.26(typescript@5.9.3) + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vue: 3.5.29(typescript@5.9.3) vite@7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: @@ -17189,7 +17032,7 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.5 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.24 @@ -17206,7 +17049,24 @@ snapshots: fdir: 6.5.0(picomatch@4.0.3) picomatch: 4.0.3 postcss: 8.5.6 - rollup: 4.52.5 + rollup: 4.59.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 20.19.24 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + terser: 5.44.0 + tsx: 4.20.3 + yaml: 2.8.2 + + vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2): + dependencies: + esbuild: 0.27.3 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.59.0 tinyglobby: 0.2.15 optionalDependencies: '@types/node': 20.19.24 @@ -17221,6 +17081,10 @@ snapshots: optionalDependencies: vite: 7.3.0(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vitefu@1.1.1(vite@7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2)): + optionalDependencies: + vite: 7.3.1(@types/node@20.19.24)(jiti@2.6.1)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.2) + vitest@4.0.14(@edge-runtime/vm@5.0.0)(@types/node@20.19.24)(happy-dom@20.0.10)(jiti@2.6.1)(jsdom@27.1.0)(lightningcss@1.30.2)(terser@5.44.0)(tsx@4.20.3)(yaml@2.8.0): dependencies: '@vitest/expect': 4.0.14 @@ -17306,7 +17170,7 @@ snapshots: vscode-languageclient@9.0.1: dependencies: - minimatch: 5.1.6 + minimatch: 5.1.9 semver: 7.7.2 vscode-languageserver-protocol: 3.17.5 @@ -17329,7 +17193,7 @@ snapshots: vue-bundle-renderer@2.2.0: dependencies: - ufo: 1.6.1 + ufo: 1.6.3 vue-demi@0.14.10(vue@3.5.22(typescript@5.9.3)): dependencies: @@ -17342,10 +17206,10 @@ snapshots: '@vue/devtools-api': 6.6.4 vue: 3.5.22(typescript@5.9.3) - vue-router@4.6.4(vue@3.5.26(typescript@5.9.3)): + vue-router@4.6.4(vue@3.5.29(typescript@5.9.3)): dependencies: '@vue/devtools-api': 6.6.4 - vue: 3.5.26(typescript@5.9.3) + vue: 3.5.29(typescript@5.9.3) vue-tsc@3.2.5(typescript@5.9.3): dependencies: @@ -17363,13 +17227,13 @@ snapshots: optionalDependencies: typescript: 5.9.3 - vue@3.5.26(typescript@5.9.3): + vue@3.5.29(typescript@5.9.3): dependencies: - '@vue/compiler-dom': 3.5.26 - '@vue/compiler-sfc': 3.5.26 - '@vue/runtime-dom': 3.5.26 - '@vue/server-renderer': 3.5.26(vue@3.5.26(typescript@5.9.3)) - '@vue/shared': 3.5.26 + '@vue/compiler-dom': 3.5.29 + '@vue/compiler-sfc': 3.5.29 + '@vue/runtime-dom': 3.5.29 + '@vue/server-renderer': 3.5.29(vue@3.5.29(typescript@5.9.3)) + '@vue/shared': 3.5.29 optionalDependencies: typescript: 5.9.3 @@ -17495,10 +17359,7 @@ snapshots: wrappy@1.0.2: {} - ws@8.18.3: {} - - ws@8.19.0: - optional: true + ws@8.19.0: {} wsl-utils@0.1.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ca0942a89..b7625c3c5 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -18,17 +18,17 @@ catalog: kysely: ~0.28.8 langium: 3.5.0 langium-cli: 3.5.0 - next: 16.0.10 - nuxt: 4.2.2 - '@sveltejs/kit': 2.49.1 + next: 16.1.6 + nuxt: 4.3.1 + '@sveltejs/kit': 2.53.2 pg: ^8.13.1 mysql2: ^3.16.1 prisma: ^6.19.0 react: 19.2.0 react-dom: 19.2.0 sql.js: ^1.13.0 - svelte: 5.45.6 - tmp: ^0.2.3 + svelte: 5.53.5 + tmp: ^0.2.4 ts-pattern: ^5.7.1 typescript: ^5.9.3 vue: 3.5.22 From 2da0c214dc84606e6bb027aa23a49d488fcea1f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 25 Feb 2026 22:40:51 -0500 Subject: [PATCH 18/25] [CI] Bump version 3.4.0 (#2408) Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- package.json | 2 +- packages/auth-adapters/better-auth/package.json | 2 +- packages/cli/package.json | 2 +- packages/clients/client-helpers/package.json | 2 +- packages/clients/tanstack-query/package.json | 2 +- packages/common-helpers/package.json | 2 +- packages/config/eslint-config/package.json | 2 +- packages/config/typescript-config/package.json | 2 +- packages/config/vitest-config/package.json | 2 +- packages/create-zenstack/package.json | 2 +- packages/ide/vscode/package.json | 2 +- packages/language/package.json | 2 +- packages/orm/package.json | 2 +- packages/plugins/policy/package.json | 2 +- packages/schema/package.json | 2 +- packages/sdk/package.json | 2 +- packages/server/package.json | 2 +- packages/testtools/package.json | 2 +- packages/zod/package.json | 2 +- samples/orm/package.json | 2 +- tests/e2e/package.json | 2 +- tests/regression/package.json | 2 +- tests/runtimes/bun/package.json | 2 +- tests/runtimes/edge-runtime/package.json | 2 +- 24 files changed, 24 insertions(+), 24 deletions(-) diff --git a/package.json b/package.json index 16a9ce13d..881b5a560 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "zenstack-v3", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack", "packageManager": "pnpm@10.23.0", "type": "module", diff --git a/packages/auth-adapters/better-auth/package.json b/packages/auth-adapters/better-auth/package.json index e19c325c4..d6003b428 100644 --- a/packages/auth-adapters/better-auth/package.json +++ b/packages/auth-adapters/better-auth/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/better-auth", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack Better Auth Adapter. This adapter is modified from better-auth's Prisma adapter.", "type": "module", "scripts": { diff --git a/packages/cli/package.json b/packages/cli/package.json index 3b59ac456..ccb98ecfc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -3,7 +3,7 @@ "publisher": "zenstack", "displayName": "ZenStack CLI", "description": "FullStack database toolkit with built-in access control and automatic API generation.", - "version": "3.3.3", + "version": "3.4.0", "type": "module", "author": { "name": "ZenStack Team" diff --git a/packages/clients/client-helpers/package.json b/packages/clients/client-helpers/package.json index d1aa2707d..d59be3eab 100644 --- a/packages/clients/client-helpers/package.json +++ b/packages/clients/client-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/client-helpers", - "version": "3.3.3", + "version": "3.4.0", "description": "Helpers for implementing clients that consume ZenStack's CRUD service", "type": "module", "scripts": { diff --git a/packages/clients/tanstack-query/package.json b/packages/clients/tanstack-query/package.json index cf174c881..325117247 100644 --- a/packages/clients/tanstack-query/package.json +++ b/packages/clients/tanstack-query/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/tanstack-query", - "version": "3.3.3", + "version": "3.4.0", "description": "TanStack Query Client for consuming ZenStack v3's CRUD service", "type": "module", "scripts": { diff --git a/packages/common-helpers/package.json b/packages/common-helpers/package.json index 84ea919e2..ca1c43877 100644 --- a/packages/common-helpers/package.json +++ b/packages/common-helpers/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/common-helpers", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack Common Helpers", "type": "module", "scripts": { diff --git a/packages/config/eslint-config/package.json b/packages/config/eslint-config/package.json index 0e263f3fa..8657fdb0b 100644 --- a/packages/config/eslint-config/package.json +++ b/packages/config/eslint-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/eslint-config", - "version": "3.3.3", + "version": "3.4.0", "type": "module", "private": true, "license": "MIT" diff --git a/packages/config/typescript-config/package.json b/packages/config/typescript-config/package.json index 10827d50e..7b0a08156 100644 --- a/packages/config/typescript-config/package.json +++ b/packages/config/typescript-config/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/typescript-config", - "version": "3.3.3", + "version": "3.4.0", "private": true, "license": "MIT" } diff --git a/packages/config/vitest-config/package.json b/packages/config/vitest-config/package.json index df6047150..549c0efe2 100644 --- a/packages/config/vitest-config/package.json +++ b/packages/config/vitest-config/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/vitest-config", "type": "module", - "version": "3.3.3", + "version": "3.4.0", "private": true, "license": "MIT", "exports": { diff --git a/packages/create-zenstack/package.json b/packages/create-zenstack/package.json index 78e5d3705..05e0478d6 100644 --- a/packages/create-zenstack/package.json +++ b/packages/create-zenstack/package.json @@ -1,6 +1,6 @@ { "name": "create-zenstack", - "version": "3.3.3", + "version": "3.4.0", "description": "Create a new ZenStack project", "type": "module", "scripts": { diff --git a/packages/ide/vscode/package.json b/packages/ide/vscode/package.json index 1d2cc32ee..2cf4b1782 100644 --- a/packages/ide/vscode/package.json +++ b/packages/ide/vscode/package.json @@ -1,7 +1,7 @@ { "name": "zenstack-v3", "publisher": "zenstack", - "version": "3.3.3", + "version": "3.4.0", "displayName": "ZenStack V3 Language Tools", "description": "VSCode extension for ZenStack (v3) ZModel language", "private": true, diff --git a/packages/language/package.json b/packages/language/package.json index 5d809e7a1..9adead079 100644 --- a/packages/language/package.json +++ b/packages/language/package.json @@ -1,7 +1,7 @@ { "name": "@zenstackhq/language", "description": "ZenStack ZModel language specification", - "version": "3.3.3", + "version": "3.4.0", "license": "MIT", "author": "ZenStack Team", "files": [ diff --git a/packages/orm/package.json b/packages/orm/package.json index ad6f1c57e..0d64ee96b 100644 --- a/packages/orm/package.json +++ b/packages/orm/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/orm", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack ORM", "type": "module", "scripts": { diff --git a/packages/plugins/policy/package.json b/packages/plugins/policy/package.json index 547fa3009..93f44704c 100644 --- a/packages/plugins/policy/package.json +++ b/packages/plugins/policy/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/plugin-policy", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack Policy Plugin", "type": "module", "scripts": { diff --git a/packages/schema/package.json b/packages/schema/package.json index 2b8b09bec..b3301dbe2 100644 --- a/packages/schema/package.json +++ b/packages/schema/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/schema", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack Runtime Schema", "type": "module", "scripts": { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index cfbb93d9f..6195a1bae 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/sdk", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack SDK", "type": "module", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index d6388aa66..aacff745d 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/server", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack automatic CRUD API handlers and server adapters", "type": "module", "scripts": { diff --git a/packages/testtools/package.json b/packages/testtools/package.json index 0197e040b..5acace729 100644 --- a/packages/testtools/package.json +++ b/packages/testtools/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/testtools", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack Test Tools", "type": "module", "scripts": { diff --git a/packages/zod/package.json b/packages/zod/package.json index ad0f66197..5ac2ee9ec 100644 --- a/packages/zod/package.json +++ b/packages/zod/package.json @@ -1,6 +1,6 @@ { "name": "@zenstackhq/zod", - "version": "3.3.3", + "version": "3.4.0", "description": "ZenStack Zod integration", "type": "module", "scripts": { diff --git a/samples/orm/package.json b/samples/orm/package.json index 712b30535..818447de1 100644 --- a/samples/orm/package.json +++ b/samples/orm/package.json @@ -1,6 +1,6 @@ { "name": "sample-orm", - "version": "3.3.3", + "version": "3.4.0", "description": "", "main": "index.js", "private": true, diff --git a/tests/e2e/package.json b/tests/e2e/package.json index 25cf8d880..fb5b50a19 100644 --- a/tests/e2e/package.json +++ b/tests/e2e/package.json @@ -1,6 +1,6 @@ { "name": "e2e", - "version": "3.3.3", + "version": "3.4.0", "private": true, "type": "module", "scripts": { diff --git a/tests/regression/package.json b/tests/regression/package.json index cbbdc2656..9e1eb5e9c 100644 --- a/tests/regression/package.json +++ b/tests/regression/package.json @@ -1,6 +1,6 @@ { "name": "regression", - "version": "3.3.3", + "version": "3.4.0", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/bun/package.json b/tests/runtimes/bun/package.json index eff1b55e2..3ff67cc3f 100644 --- a/tests/runtimes/bun/package.json +++ b/tests/runtimes/bun/package.json @@ -1,6 +1,6 @@ { "name": "bun-e2e", - "version": "3.3.3", + "version": "3.4.0", "private": true, "type": "module", "scripts": { diff --git a/tests/runtimes/edge-runtime/package.json b/tests/runtimes/edge-runtime/package.json index 07b7b068a..d38cdffe5 100644 --- a/tests/runtimes/edge-runtime/package.json +++ b/tests/runtimes/edge-runtime/package.json @@ -1,6 +1,6 @@ { "name": "edge-runtime-e2e", - "version": "3.3.3", + "version": "3.4.0", "private": true, "type": "module", "scripts": { From 851e744ba5e83e58789fd221f1132d7e00e92aea Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 26 Feb 2026 20:47:51 -0500 Subject: [PATCH 19/25] fix(cli): update version check tag to latest (#2412) --- packages/cli/src/utils/version-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/cli/src/utils/version-utils.ts b/packages/cli/src/utils/version-utils.ts index ad428cdf5..e9749e878 100644 --- a/packages/cli/src/utils/version-utils.ts +++ b/packages/cli/src/utils/version-utils.ts @@ -5,7 +5,7 @@ import { fileURLToPath } from 'node:url'; import semver from 'semver'; const CHECK_VERSION_TIMEOUT = 2000; -const VERSION_CHECK_TAG = 'next'; +const VERSION_CHECK_TAG = 'latest'; export function getVersion() { try { From 75bc4a1937b804a035298fb43c4f007a0ac3aaaa Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Thu, 26 Feb 2026 21:02:36 -0500 Subject: [PATCH 20/25] fix(policy): wrong table alias used when injecting for field policies (#2413) --- packages/plugins/policy/src/policy-handler.ts | 2 +- tests/regression/test/issue-2385.test.ts | 53 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 tests/regression/test/issue-2385.test.ts diff --git a/packages/plugins/policy/src/policy-handler.ts b/packages/plugins/policy/src/policy-handler.ts index aa46be502..f42459bc4 100644 --- a/packages/plugins/policy/src/policy-handler.ts +++ b/packages/plugins/policy/src/policy-handler.ts @@ -660,7 +660,7 @@ export class PolicyHandler extends OperationNodeTransf selections = [SelectionNode.create(SelectAllNode.create())]; } - const modelPolicyFilter = this.buildPolicyFilter(model, alias, operation); + const modelPolicyFilter = this.buildPolicyFilter(model, model, operation); if (!isTrueNode(modelPolicyFilter)) { hasPolicies = true; } diff --git a/tests/regression/test/issue-2385.test.ts b/tests/regression/test/issue-2385.test.ts new file mode 100644 index 000000000..6c1ad11aa --- /dev/null +++ b/tests/regression/test/issue-2385.test.ts @@ -0,0 +1,53 @@ +import { createPolicyTestClient } from '@zenstackhq/testtools'; +import { describe, expect, it } from 'vitest'; + +describe('Regression for issue #2385', () => { + it('should not generate "missing FROM-clause entry" when including a relation with a static-value @@allow policy', async () => { + const db = await createPolicyTestClient( + ` +model User { + id String @id @default(uuid()) + name String + associations Association[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@allow("read", auth().id == id) +} + +model Association { + id String @id @default(uuid()) + name String + userId String @db.VarChar @map("user_id") + user User @relation(fields: [userId], references: [id]) + createdAt DateTime @default(now()) @allow("read", auth().id == userId) + updatedAt DateTime @updatedAt + + @@allow("read", auth().id == userId) +} + `, + ); + + const rawDb = db.$unuseAll(); + const user = await rawDb.user.create({ + data: { + name: 'John Doe', + }, + }); + + await rawDb.association.create({ + data: { + name: 'Association for John', + userId: user.id, + }, + }); + + const userDb = db.$setAuth(user); + + await expect( + userDb.user.findMany({ + include: { associations: true }, + }), + ).toResolveTruthy(); + }); +}); From 4fe27ef8be1f202695c91d65fe812716a09a0416 Mon Sep 17 00:00:00 2001 From: Jiasheng Date: Sat, 28 Feb 2026 02:44:01 +0800 Subject: [PATCH 21/25] chore(cli): show notifications for generate command (#2409) Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- packages/cli/package.json | 10 ++-- packages/cli/src/actions/action-utils.ts | 68 ++++++++++++++++++++++++ packages/cli/src/actions/generate.ts | 20 +++++-- packages/cli/src/index.ts | 2 + pnpm-lock.yaml | 44 +++++++++++++++ scripts/test-generate.ts | 2 +- 6 files changed, 137 insertions(+), 9 deletions(-) diff --git a/packages/cli/package.json b/packages/cli/package.json index ccb98ecfc..ee902debf 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -37,9 +37,9 @@ }, "dependencies": { "@zenstackhq/common-helpers": "workspace:*", - "@zenstackhq/schema": "workspace:*", "@zenstackhq/language": "workspace:*", "@zenstackhq/orm": "workspace:*", + "@zenstackhq/schema": "workspace:*", "@zenstackhq/sdk": "workspace:*", "@zenstackhq/server": "workspace:*", "chokidar": "^5.0.0", @@ -56,7 +56,9 @@ "package-manager-detector": "^1.3.0", "prisma": "catalog:", "semver": "^7.7.2", - "ts-pattern": "catalog:" + "terminal-link": "^5.0.0", + "ts-pattern": "catalog:", + "zod": "catalog:" }, "devDependencies": { "@types/better-sqlite3": "catalog:", @@ -72,9 +74,9 @@ "tmp": "catalog:" }, "peerDependencies": { - "pg": "catalog:", "better-sqlite3": "catalog:", - "mysql2": "catalog:" + "mysql2": "catalog:", + "pg": "catalog:" }, "peerDependenciesMeta": { "pg": { diff --git a/packages/cli/src/actions/action-utils.ts b/packages/cli/src/actions/action-utils.ts index c33a81d64..7539c4ca0 100644 --- a/packages/cli/src/actions/action-utils.ts +++ b/packages/cli/src/actions/action-utils.ts @@ -6,6 +6,8 @@ import fs from 'node:fs'; import { createRequire } from 'node:module'; import path from 'node:path'; import { CliError } from '../cli-error'; +import terminalLink from 'terminal-link'; +import { z } from 'zod'; export function getSchemaFile(file?: string) { if (file) { @@ -216,3 +218,69 @@ export async function getZenStackPackages( return result.filter((p) => !!p); } + +const FETCH_CLI_MAX_TIME = 1000; +const CLI_CONFIG_ENDPOINT = 'https://zenstack.dev/config/cli-v3.json'; + +const usageTipsSchema = z.object({ + notifications: z.array(z.object({ title: z.string(), url: z.url().optional(), active: z.boolean() })), +}); + +/** + * Starts the usage tips fetch in the background. Returns a callback that, when invoked check if the fetch + * is complete. If not complete, it will wait until the max time is reached. After that, if fetch is still + * not complete, just return. + */ +export function startUsageTipsFetch() { + let fetchedData: z.infer | undefined = undefined; + let fetchComplete = false; + + const start = Date.now(); + const controller = new AbortController(); + + fetch(CLI_CONFIG_ENDPOINT, { + headers: { accept: 'application/json' }, + signal: controller.signal, + }) + .then(async (res) => { + if (!res.ok) return; + const data = await res.json(); + const parseResult = usageTipsSchema.safeParse(data); + if (parseResult.success) { + fetchedData = parseResult.data; + } + }) + .catch(() => { + // noop + }) + .finally(() => { + fetchComplete = true; + }); + + return async () => { + const elapsed = Date.now() - start; + + if (!fetchComplete && elapsed < FETCH_CLI_MAX_TIME) { + // wait for the timeout + await new Promise((resolve) => setTimeout(resolve, FETCH_CLI_MAX_TIME - elapsed)); + } + + if (!fetchComplete) { + controller.abort(); + return; + } + + if (!fetchedData) return; + + const activeItems = fetchedData.notifications.filter((item) => item.active); + // show a random active item + if (activeItems.length > 0) { + const item = activeItems[Math.floor(Math.random() * activeItems.length)]!; + if (item.url) { + console.log(terminalLink(item.title, item.url)); + } else { + console.log(item.title); + } + } + }; +} diff --git a/packages/cli/src/actions/generate.ts b/packages/cli/src/actions/generate.ts index 351ecceb0..fe31688b8 100644 --- a/packages/cli/src/actions/generate.ts +++ b/packages/cli/src/actions/generate.ts @@ -1,19 +1,25 @@ import { invariant, singleDebounce } from '@zenstackhq/common-helpers'; import { ZModelLanguageMetaData } from '@zenstackhq/language'; -import { type AbstractDeclaration, isPlugin, LiteralExpr, Plugin, type Model } from '@zenstackhq/language/ast'; +import { isPlugin, LiteralExpr, Plugin, type AbstractDeclaration, type Model } from '@zenstackhq/language/ast'; import { getLiteral, getLiteralArray } from '@zenstackhq/language/utils'; import { type CliPlugin } from '@zenstackhq/sdk'; +import { watch } from 'chokidar'; import colors from 'colors'; import { createJiti } from 'jiti'; import fs from 'node:fs'; import path from 'node:path'; import { pathToFileURL } from 'node:url'; -import { watch } from 'chokidar'; import ora, { type Ora } from 'ora'; +import semver from 'semver'; import { CliError } from '../cli-error'; import * as corePlugins from '../plugins'; -import { getOutputPath, getSchemaFile, getZenStackPackages, loadSchemaDocument } from './action-utils'; -import semver from 'semver'; +import { + getOutputPath, + getSchemaFile, + getZenStackPackages, + loadSchemaDocument, + startUsageTipsFetch, +} from './action-utils'; type Options = { schema?: string; @@ -24,6 +30,7 @@ type Options = { liteOnly?: boolean; generateModels?: boolean; generateInput?: boolean; + tips?: boolean; }; /** @@ -35,8 +42,13 @@ export async function run(options: Options) { } catch (err) { console.warn(colors.yellow(`Failed to check for mismatched ZenStack packages: ${err}`)); } + + const maybeShowUsageTips = options.tips && !options.silent && !options.watch ? startUsageTipsFetch() : undefined; + const model = await pureGenerate(options, false); + await maybeShowUsageTips?.(); + if (options.watch) { const logsEnabled = !options.silent; diff --git a/packages/cli/src/index.ts b/packages/cli/src/index.ts index fcc4685c3..bdeca9b5d 100644 --- a/packages/cli/src/index.ts +++ b/packages/cli/src/index.ts @@ -74,12 +74,14 @@ function createProgram() { ); const noVersionCheckOption = new Option('--no-version-check', 'do not check for new version'); + const noTipsOption = new Option('--no-tips', 'do not show usage tips'); program .command('generate') .description('Run code generation plugins') .addOption(schemaOption) .addOption(noVersionCheckOption) + .addOption(noTipsOption) .addOption(new Option('-o, --output ', 'default output directory for code generation')) .addOption(new Option('-w, --watch', 'enable watch mode').default(false)) .addOption( diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 95980553f..c5ac83d9e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -266,9 +266,15 @@ importers: semver: specifier: ^7.7.2 version: 7.7.2 + terminal-link: + specifier: ^5.0.0 + version: 5.0.0 ts-pattern: specifier: 'catalog:' version: 5.7.1 + zod: + specifier: 'catalog:' + version: 4.1.12 devDependencies: '@types/better-sqlite3': specifier: 'catalog:' @@ -4213,6 +4219,10 @@ packages: alien-signals@3.0.3: resolution: {integrity: sha512-2JXjom6R7ZwrISpUphLhf4htUq1aKRCennTJ6u9kFfr3sLmC9+I4CxxVi+McoFnIg+p1HnVrfLT/iCt4Dlz//Q==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -5236,6 +5246,10 @@ packages: resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} engines: {node: '>=0.12'} + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -5790,6 +5804,10 @@ packages: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} + has-flag@5.0.1: + resolution: {integrity: sha512-CsNUt5x9LUdx6hnk/E2SZLsDyvfqANZSUq4+D3D8RzDJ2M+HDTIkF60ibS1vHaK55vzgiZw1bEPFG9yH7l33wA==} + engines: {node: '>=12'} + has-property-descriptors@1.0.2: resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} @@ -8057,6 +8075,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-hyperlinks@4.4.0: + resolution: {integrity: sha512-UKbpT93hN5Nr9go5UY7bopIB9YQlMz9nm/ct4IXt/irb5YRkn9WaqrOBJGZ5Pwvsd5FQzSVeYlGdXoCAPQZrPg==} + engines: {node: '>=20'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -8119,6 +8141,10 @@ packages: resolution: {integrity: sha512-BTLcK0xsDh2+PUe9F6c2TlRp4zOOBMTkoQHQIWSIzI0R7KG46uEwq4OPk2W7bZcprBMsuaeFsqwYr7pjh6CuHg==} engines: {node: '>=18'} + terminal-link@5.0.0: + resolution: {integrity: sha512-qFAy10MTMwjzjU8U16YS4YoZD+NQLHzLssFMNqgravjbvIPNiqkGFR4yjhJfmY9R5OFU7+yHxc6y+uGHkKwLRA==} + engines: {node: '>=20'} + terser@5.44.0: resolution: {integrity: sha512-nIVck8DK+GM/0Frwd+nIhZ84pR/BX7rmXMfYwyg+Sri5oGVE99/E3KvXqpC2xHFxyqXyGHTKBSioxxplrO4I4w==} engines: {node: '>=10'} @@ -12065,6 +12091,10 @@ snapshots: alien-signals@3.0.3: {} + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -12987,6 +13017,8 @@ snapshots: entities@7.0.1: {} + environment@1.1.0: {} + error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -13841,6 +13873,8 @@ snapshots: has-flag@4.0.0: {} + has-flag@5.0.1: {} + has-property-descriptors@1.0.2: dependencies: es-define-property: 1.0.1 @@ -16403,6 +16437,11 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-hyperlinks@4.4.0: + dependencies: + has-flag: 5.0.1 + supports-color: 10.2.2 + supports-preserve-symlinks-flag@1.0.0: {} svelte-check@4.3.5(picomatch@4.0.3)(svelte@5.53.5)(typescript@5.9.3): @@ -16498,6 +16537,11 @@ snapshots: minizlib: 3.1.0 yallist: 5.0.0 + terminal-link@5.0.0: + dependencies: + ansi-escapes: 7.3.0 + supports-hyperlinks: 4.4.0 + terser@5.44.0: dependencies: '@jridgewell/source-map': 0.3.11 diff --git a/scripts/test-generate.ts b/scripts/test-generate.ts index 0af24290e..890d26b8e 100644 --- a/scripts/test-generate.ts +++ b/scripts/test-generate.ts @@ -22,7 +22,7 @@ async function generate(schemaPath: string, options: string[]) { const cliPath = path.join(_dirname, '../packages/cli/dist/index.js'); const RUNTIME = process.env.RUNTIME ?? 'node'; execSync( - `${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false`, + `${RUNTIME} ${cliPath} generate --schema ${schemaPath} ${options.join(' ')} --generate-models=false --generate-input=false --no-version-check --no-tips`, { cwd: path.dirname(schemaPath), }, From e5e452cf0a38021326086efdad9a054c7ec2a588 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:36:17 -0800 Subject: [PATCH 22/25] Fix: disconnect correct client instance in zod test `finally` block (#2415) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- tests/e2e/orm/client-api/zod.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/e2e/orm/client-api/zod.test.ts b/tests/e2e/orm/client-api/zod.test.ts index 586782f80..abee059d5 100644 --- a/tests/e2e/orm/client-api/zod.test.ts +++ b/tests/e2e/orm/client-api/zod.test.ts @@ -1003,14 +1003,14 @@ describe('Zod schema factory test', () => { describe('create factory functions tests', () => { it('can be constructed directly from client', async () => { + const localClient = await createTestClient(schema); try { - const client = await createTestClient(schema); - const factory = createQuerySchemaFactory(client); + const factory = createQuerySchemaFactory(localClient); const s = factory.makeFindManySchema('User'); expect(s.safeParse({ where: { email: 'u@test.com' } }).success).toBe(true); expect(s.safeParse({ where: { notAField: 'val' } }).success).toBe(false); } finally { - await client.$disconnect(); + await localClient.$disconnect(); } }); From 7c83f7c995a1b0cf1a8cb59ecf86858d100df140 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Fri, 27 Feb 2026 11:36:31 -0800 Subject: [PATCH 23/25] Fix order-dependent assertions in slicing E2E tests (#2416) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: ymc9 <104139426+ymc9@users.noreply.github.com> --- tests/e2e/orm/client-api/slicing.test.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/e2e/orm/client-api/slicing.test.ts b/tests/e2e/orm/client-api/slicing.test.ts index 5ba5fcb35..cf924d20c 100644 --- a/tests/e2e/orm/client-api/slicing.test.ts +++ b/tests/e2e/orm/client-api/slicing.test.ts @@ -505,7 +505,9 @@ describe('Query slicing tests', () => { }); expect(user.posts).toHaveLength(2); - expect(user.posts[0]!.title).toBe('Post 1'); + expect(user.posts).toEqual( + expect.arrayContaining([expect.objectContaining({ title: 'Post 1' })]) + ); }); it('allows nested update on included models', async () => { @@ -545,7 +547,8 @@ describe('Query slicing tests', () => { include: { posts: true }, }); - expect(updated.posts[0]!.title).toBe('Updated Post'); + const updatedPost = updated.posts.find((p) => p.id === postId); + expect(updatedPost!.title).toBe('Updated Post'); }); }); From e46ddc576a8d0f38308d0b80dafd274f2002074c Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Fri, 27 Feb 2026 19:43:47 -0500 Subject: [PATCH 24/25] fix(zod): exclude computed and delegate fields from create/update schemas (#2418) --- packages/zod/src/factory.ts | 6 +- packages/zod/src/types.ts | 14 +- packages/zod/test/factory.test.ts | 210 +++++++++++++++++++++++++ packages/zod/test/schema/schema.ts | 151 ++++++++++++++++++ packages/zod/test/schema/schema.zmodel | 28 ++++ packages/zod/vitest.config.ts | 12 +- 6 files changed, 416 insertions(+), 5 deletions(-) diff --git a/packages/zod/src/factory.ts b/packages/zod/src/factory.ts index 97f759690..dece3e27e 100644 --- a/packages/zod/src/factory.ts +++ b/packages/zod/src/factory.ts @@ -71,7 +71,8 @@ class SchemaFactory { const fields: Record = {}; for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { - if (fieldDef.relation) { + // exclude relation, computed, delegate discriminator fields + if (fieldDef.relation || fieldDef.computed || fieldDef.isDiscriminator) { continue; } @@ -96,7 +97,8 @@ class SchemaFactory { const fields: Record = {}; for (const [fieldName, fieldDef] of Object.entries(modelDef.fields)) { - if (fieldDef.relation) { + // exclude relation, computed, delegate discriminator fields + if (fieldDef.relation || fieldDef.computed || fieldDef.isDiscriminator) { continue; } diff --git a/packages/zod/src/types.ts b/packages/zod/src/types.ts index 6bc5a7c80..b8d1b05b0 100644 --- a/packages/zod/src/types.ts +++ b/packages/zod/src/types.ts @@ -1,6 +1,8 @@ import type { FieldHasDefault, FieldIsArray, + FieldIsComputed, + FieldIsDelegateDiscriminator, FieldIsRelation, GetEnum, GetEnums, @@ -51,7 +53,11 @@ export type GetModelFieldsShape> = { [Field in GetModelFields as FieldIsRelation extends true ? never - : Field]: ZodOptionalIf< + : FieldIsComputed extends true + ? never + : FieldIsDelegateDiscriminator extends true + ? never + : Field]: ZodOptionalIf< ZodOptionalAndNullableIf, ModelFieldIsOptional>, FieldHasDefault >; @@ -60,7 +66,11 @@ export type GetModelCreateFieldsShape> = { [Field in GetModelFields as FieldIsRelation extends true ? never - : Field]: z.ZodOptional< + : FieldIsComputed extends true + ? never + : FieldIsDelegateDiscriminator extends true + ? never + : Field]: z.ZodOptional< ZodOptionalAndNullableIf, ModelFieldIsOptional> >; }; diff --git a/packages/zod/test/factory.test.ts b/packages/zod/test/factory.test.ts index 3cd16af15..e8368a385 100644 --- a/packages/zod/test/factory.test.ts +++ b/packages/zod/test/factory.test.ts @@ -636,3 +636,213 @@ describe('SchemaFactory - makeEnumSchema', () => { expect(() => factory.makeEnumSchema('Unknown' as any)).toThrow(); }); }); + +// --- Computed fields tests --- + +const validProduct = { + id: 'prod1', + name: 'Widget', + price: 10.0, + discount: 2.0, + finalPrice: 8.0, +}; + +describe('SchemaFactory - computed fields', () => { + describe('makeModelSchema includes computed fields', () => { + it('accepts a Product with computed field present', () => { + const productSchema = factory.makeModelSchema('Product'); + expect(productSchema.safeParse(validProduct).success).toBe(true); + }); + + it('rejects a Product missing the computed field', () => { + const productSchema = factory.makeModelSchema('Product'); + const { finalPrice: _, ...withoutComputed } = validProduct; + expect(productSchema.safeParse(withoutComputed).success).toBe(false); + }); + + it('infers computed field in model schema type', () => { + const _schema = factory.makeModelSchema('Product'); + type Product = z.infer; + expectTypeOf().toEqualTypeOf(); + }); + }); + + describe('makeModelCreateSchema excludes computed fields', () => { + it('accepts a Product without the computed field', () => { + const createSchema = factory.makeModelCreateSchema('Product'); + expect(createSchema.safeParse({ name: 'Widget', price: 10.0 }).success).toBe(true); + }); + + it('rejects a Product with the computed field (strict)', () => { + const createSchema = factory.makeModelCreateSchema('Product'); + expect(createSchema.safeParse({ name: 'Widget', price: 10.0, finalPrice: 8.0 }).success).toBe(false); + }); + + it('does not include computed field in create schema type', () => { + const _schema = factory.makeModelCreateSchema('Product'); + type ProductCreate = z.infer; + expectTypeOf().not.toHaveProperty('finalPrice'); + // own fields are present + expectTypeOf().toHaveProperty('name'); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + // field with default is optional + expectTypeOf().toHaveProperty('discount'); + }); + }); + + describe('makeModelUpdateSchema excludes computed fields', () => { + it('accepts a Product update without the computed field', () => { + const updateSchema = factory.makeModelUpdateSchema('Product'); + expect(updateSchema.safeParse({ price: 12.0 }).success).toBe(true); + }); + + it('rejects a Product update with the computed field (strict)', () => { + const updateSchema = factory.makeModelUpdateSchema('Product'); + expect(updateSchema.safeParse({ price: 12.0, finalPrice: 10.0 }).success).toBe(false); + }); + + it('does not include computed field in update schema type', () => { + const _schema = factory.makeModelUpdateSchema('Product'); + type ProductUpdate = z.infer; + expectTypeOf().not.toHaveProperty('finalPrice'); + // own fields are present (all optional in update) + expectTypeOf().toHaveProperty('name'); + }); + }); +}); + +// --- Delegate model tests --- + +const validVideo = { + id: 1, + createdAt: new Date(), + assetType: 'Video', + duration: 120, + url: 'https://example.com/video.mp4', +}; + +const validImage = { + id: 2, + createdAt: new Date(), + assetType: 'Image', + format: 'png', + width: 800, +}; + +describe('SchemaFactory - delegate models', () => { + describe('makeModelSchema for delegate base model', () => { + it('accepts a valid Asset', () => { + const assetSchema = factory.makeModelSchema('Asset'); + expect(assetSchema.safeParse({ id: 1, createdAt: new Date(), assetType: 'Video' }).success).toBe(true); + }); + + it('includes discriminator field in model schema type', () => { + const _schema = factory.makeModelSchema('Asset'); + type Asset = z.infer; + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); + }); + + describe('makeModelSchema for derived models', () => { + it('accepts a valid Video (includes inherited + own fields)', () => { + const videoSchema = factory.makeModelSchema('Video'); + expect(videoSchema.safeParse(validVideo).success).toBe(true); + }); + + it('accepts a valid Image (includes inherited + own fields)', () => { + const imageSchema = factory.makeModelSchema('Image'); + expect(imageSchema.safeParse(validImage).success).toBe(true); + }); + + it('rejects Video missing own fields', () => { + const videoSchema = factory.makeModelSchema('Video'); + const { duration: _, url: _u, ...withoutOwn } = validVideo; + expect(videoSchema.safeParse(withoutOwn).success).toBe(false); + }); + + it('infers correct types for derived model including inherited fields', () => { + const _schema = factory.makeModelSchema('Video'); + type Video = z.infer; + // inherited fields + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + // own fields + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); + }); + + describe('makeModelCreateSchema excludes discriminator', () => { + it('accepts Video create without discriminator and inherited fields', () => { + const createSchema = factory.makeModelCreateSchema('Video'); + // Only own non-inherited, non-discriminator fields should be required + expect(createSchema.safeParse({ duration: 120, url: 'https://example.com/video.mp4' }).success).toBe(true); + }); + + it('rejects Video create with discriminator field (strict)', () => { + const createSchema = factory.makeModelCreateSchema('Video'); + expect( + createSchema.safeParse({ + duration: 120, + url: 'https://example.com/video.mp4', + assetType: 'Video', + }).success, + ).toBe(false); + }); + + it('does not include discriminator fields in create schema type', () => { + const _schema = factory.makeModelCreateSchema('Video'); + type VideoCreate = z.infer; + // discriminator and originModel fields should be excluded + expectTypeOf().not.toHaveProperty('assetType'); + // own fields should be present + expectTypeOf().toHaveProperty('duration'); + expectTypeOf().toHaveProperty('url'); + expectTypeOf().toEqualTypeOf(); + expectTypeOf().toEqualTypeOf(); + }); + + it('excludes discriminator from base delegate create schema', () => { + const createSchema = factory.makeModelCreateSchema('Asset'); + // discriminator should not be included + expect(createSchema.safeParse({ assetType: 'Video' }).success).toBe(false); + // empty create (id has default, createdAt has default, assetType is discriminator) + expect(createSchema.safeParse({}).success).toBe(true); + }); + + it('does not include discriminator in base delegate create schema type', () => { + const _schema = factory.makeModelCreateSchema('Asset'); + type AssetCreate = z.infer; + expectTypeOf().not.toHaveProperty('assetType'); + }); + }); + + describe('makeModelUpdateSchema excludes discriminator and originModel fields', () => { + it('accepts Video update with only own fields', () => { + const updateSchema = factory.makeModelUpdateSchema('Video'); + expect(updateSchema.safeParse({ duration: 180 }).success).toBe(true); + }); + + it('rejects Video update with discriminator field (strict)', () => { + const updateSchema = factory.makeModelUpdateSchema('Video'); + expect(updateSchema.safeParse({ duration: 180, assetType: 'Video' }).success).toBe(false); + }); + + it('does not include discriminator fields in update schema type', () => { + const _schema = factory.makeModelUpdateSchema('Video'); + type VideoUpdate = z.infer; + expectTypeOf().not.toHaveProperty('assetType'); + // own fields should be present (all optional in update) + expectTypeOf().toHaveProperty('duration'); + expectTypeOf().toHaveProperty('url'); + }); + + it('does not include discriminator in base delegate update schema type', () => { + const _schema = factory.makeModelUpdateSchema('Asset'); + type AssetUpdate = z.infer; + expectTypeOf().not.toHaveProperty('assetType'); + }); + }); +}); diff --git a/packages/zod/test/schema/schema.ts b/packages/zod/test/schema/schema.ts index 1336683ef..e2cdd212d 100644 --- a/packages/zod/test/schema/schema.ts +++ b/packages/zod/test/schema/schema.ts @@ -145,6 +145,157 @@ export class SchemaType implements SchemaDef { uniqueFields: { id: { type: "String" } } + }, + Product: { + name: "Product", + fields: { + id: { + name: "id", + type: "String", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("cuid") }] }], + default: ExpressionUtils.call("cuid") + }, + name: { + name: "name", + type: "String" + }, + price: { + name: "price", + type: "Float" + }, + discount: { + name: "discount", + type: "Float", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.literal(0) }] }], + default: 0 + }, + finalPrice: { + name: "finalPrice", + type: "Float", + attributes: [{ name: "@computed" }], + computed: true + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "String" } + }, + computedFields: { + finalPrice(_context: { + modelAlias: string; + }): number { + throw new Error("This is a stub for computed field"); + } + } + }, + Asset: { + name: "Asset", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + createdAt: { + name: "createdAt", + type: "DateTime", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], + default: ExpressionUtils.call("now") + }, + assetType: { + name: "assetType", + type: "String", + isDiscriminator: true + } + }, + attributes: [ + { name: "@@delegate", args: [{ name: "discriminator", value: ExpressionUtils.field("assetType") }] } + ], + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + }, + isDelegate: true, + subModels: ["Video", "Image"] + }, + Video: { + name: "Video", + baseModel: "Asset", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + createdAt: { + name: "createdAt", + type: "DateTime", + originModel: "Asset", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], + default: ExpressionUtils.call("now") + }, + assetType: { + name: "assetType", + type: "String", + originModel: "Asset", + isDiscriminator: true + }, + duration: { + name: "duration", + type: "Int" + }, + url: { + name: "url", + type: "String" + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } + }, + Image: { + name: "Image", + baseModel: "Asset", + fields: { + id: { + name: "id", + type: "Int", + id: true, + attributes: [{ name: "@id" }, { name: "@default", args: [{ name: "value", value: ExpressionUtils.call("autoincrement") }] }], + default: ExpressionUtils.call("autoincrement") + }, + createdAt: { + name: "createdAt", + type: "DateTime", + originModel: "Asset", + attributes: [{ name: "@default", args: [{ name: "value", value: ExpressionUtils.call("now") }] }], + default: ExpressionUtils.call("now") + }, + assetType: { + name: "assetType", + type: "String", + originModel: "Asset", + isDiscriminator: true + }, + format: { + name: "format", + type: "String" + }, + width: { + name: "width", + type: "Int" + } + }, + idFields: ["id"], + uniqueFields: { + id: { type: "Int" } + } } } as const; typeDefs = { diff --git a/packages/zod/test/schema/schema.zmodel b/packages/zod/test/schema/schema.zmodel index 348206ac2..07e26a1ec 100644 --- a/packages/zod/test/schema/schema.zmodel +++ b/packages/zod/test/schema/schema.zmodel @@ -48,3 +48,31 @@ model Post { author User? @relation(fields: [authorId], references: [id]) authorId String? } + +// --- Computed fields --- +model Product { + id String @id @default(cuid()) + name String + price Float + discount Float @default(0) + finalPrice Float @computed +} + +// --- Delegate models --- +model Asset { + id Int @id @default(autoincrement()) + createdAt DateTime @default(now()) + assetType String + + @@delegate(assetType) +} + +model Video extends Asset { + duration Int + url String +} + +model Image extends Asset { + format String + width Int +} diff --git a/packages/zod/vitest.config.ts b/packages/zod/vitest.config.ts index 75a9f709c..96478c06d 100644 --- a/packages/zod/vitest.config.ts +++ b/packages/zod/vitest.config.ts @@ -1,4 +1,14 @@ import base from '@zenstackhq/vitest-config/base'; import { defineConfig, mergeConfig } from 'vitest/config'; -export default mergeConfig(base, defineConfig({})); +export default mergeConfig( + base, + defineConfig({ + test: { + typecheck: { + enabled: true, + include: ['test/**/*.ts'], + }, + }, + }), +); From 4b42ed992547c26f88a8e24766aa6d41023b8f61 Mon Sep 17 00:00:00 2001 From: Yiming Cao Date: Fri, 27 Feb 2026 20:54:09 -0500 Subject: [PATCH 25/25] fix(orm): disallow create/update on computed fields and delegate discriminator fields (#2419) --- packages/orm/src/client/zod/factory.ts | 24 +++++-------- .../orm/client-api/computed-fields.test.ts | 36 +++++++++++++++++++ tests/e2e/orm/client-api/delegate.test.ts | 26 ++++++++++++-- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/packages/orm/src/client/zod/factory.ts b/packages/orm/src/client/zod/factory.ts index 5a985c74c..0f0eb61e8 100644 --- a/packages/orm/src/client/zod/factory.ts +++ b/packages/orm/src/client/zod/factory.ts @@ -44,7 +44,6 @@ import type { ClientOptions } from '../options'; import type { AnyPlugin, ExtQueryArgsBase, RuntimePlugin } from '../plugin'; import { fieldHasDefaultValue, - getDiscriminatorField, getEnum, getTypeDef, getUniqueFields, @@ -1181,13 +1180,11 @@ export class ZodSchemaFactory< if (withoutFields.includes(field)) { return; } + const fieldDef = requireField(this.schema, model, field); - if (fieldDef.computed) { - return; - } - if (this.isDelegateDiscriminator(fieldDef)) { - // discriminator field is auto-assigned + // skip computed fields and discriminator fields, they cannot be set on create + if (fieldDef.computed || fieldDef.isDiscriminator) { return; } @@ -1294,15 +1291,6 @@ export class ZodSchemaFactory< } } - private isDelegateDiscriminator(fieldDef: FieldDef) { - if (!fieldDef.originModel) { - // not inherited from a delegate - return false; - } - const discriminatorField = getDiscriminatorField(this.schema, fieldDef.originModel); - return discriminatorField === fieldDef.name; - } - @cache() private makeRelationManipulationSchema( model: string, @@ -1546,8 +1534,14 @@ export class ZodSchemaFactory< if (withoutFields.includes(field)) { return; } + const fieldDef = requireField(this.schema, model, field); + if (fieldDef.computed || fieldDef.isDiscriminator) { + // skip computed fields and discriminator fields, they cannot be updated + return; + } + if (fieldDef.relation) { if (skipRelations) { return; diff --git a/tests/e2e/orm/client-api/computed-fields.test.ts b/tests/e2e/orm/client-api/computed-fields.test.ts index 1816854b8..e915b97bb 100644 --- a/tests/e2e/orm/client-api/computed-fields.test.ts +++ b/tests/e2e/orm/client-api/computed-fields.test.ts @@ -374,4 +374,40 @@ model Post extends Content { ); } }); + + it('rejects creating or updating computed fields', async () => { + const db = await createTestClient( + ` +model User { + id Int @id @default(autoincrement()) + name String + upperName String @computed +} +`, + { + computedFields: { + User: { + upperName: (eb: any) => eb.fn('upper', ['name']), + }, + }, + } as any, + ); + + await expect( + db.user.create({ + data: { id: 1, name: 'Alex', upperName: 'SHOULD NOT WORK' }, + }), + ).toBeRejectedByValidation(['upperName']); + + await db.user.create({ + data: { id: 1, name: 'Alex' }, + }); + + await expect( + db.user.update({ + where: { id: 1 }, + data: { upperName: 'STILL SHOULD NOT WORK' }, + }), + ).toBeRejectedByValidation(['upperName']); + }); }); diff --git a/tests/e2e/orm/client-api/delegate.test.ts b/tests/e2e/orm/client-api/delegate.test.ts index 86010b4e5..6ed0b9ea2 100644 --- a/tests/e2e/orm/client-api/delegate.test.ts +++ b/tests/e2e/orm/client-api/delegate.test.ts @@ -27,7 +27,6 @@ describe('Delegate model tests ', () => { data: { duration: 100, url: 'abc', - videoType: 'MyVideo', }, }), ).rejects.toThrow('is a delegate'); @@ -36,7 +35,7 @@ describe('Delegate model tests ', () => { data: { assets: { // @ts-expect-error - create: { assetType: 'Video' }, + create: {}, }, }, }), @@ -94,6 +93,19 @@ describe('Delegate model tests ', () => { galleryId: expect.any(Number), assetType: 'Image', }); + + // discriminator field cannot be set on create + await expect( + client.ratedVideo.create({ + data: { + duration: 100, + url: 'abc', + rating: 5, + // @ts-expect-error + videoType: 'RatedVideo', + }, + }), + ).toBeRejectedByValidation(['videoType']); }); it('works with createMany', async () => { @@ -636,6 +648,15 @@ describe('Delegate model tests ', () => { id: 2, viewCount: 3, }); + + // discriminator field cannot be updated + await expect( + client.ratedVideo.update({ + where: { id: 2 }, + // @ts-expect-error + data: { videoType: 'MyVideo' }, + }), + ).toBeRejectedByValidation(['videoType']); }); it('works with nested update', async () => { @@ -838,7 +859,6 @@ describe('Delegate model tests ', () => { where: { id: 2 }, create: { viewCount: 10, - assetType: 'Video', }, update: { viewCount: { increment: 1 },