diff --git a/.changeset/shiny-crews-film.md b/.changeset/shiny-crews-film.md new file mode 100644 index 00000000000..884903e6753 --- /dev/null +++ b/.changeset/shiny-crews-film.md @@ -0,0 +1,44 @@ +--- +'@builder.io/qwik-city': minor +--- + +✨ **(EXPERIMENTAL)** standardSchema$ validator (by [@nothendev](https://github.com/nothendev) in [#8840](https://github.com/QwikDev/qwik/pull/8840)) + +To use it, you need to pass `experimental: ['standardSchema']` as an option to the `qwikVite` plugin as such: + +```ts +// vite.config.ts + +export default defineConfig(({ command, mode }): UserConfig => { + return { + plugins: [ + // ... other plugins like qwikCity() etc + qwikVite({ + experimental: ['standardSchema'] + // ... other options + }), + + ], + // ... rest of the config + }; +} +``` + +Then pass any [Standard Schema compatible](https://standardschema.dev) validator into `standardSchema$`: + +```ts +import { type } from 'arktype'; +import { routeAction$, standardSchema$ } from '@builder.io/qwik-city'; + +export const useLogin = routeAction$( + async ({ username, password }, ev) => { + // ... use the type-inferred { username: string, password: string } + }, + standardSchema$( + type({ + username: 'string', + password: 'string > 8', + }) + ) +); +``` diff --git a/e2e/docs-e2e/package.json b/e2e/docs-e2e/package.json index 9830826ac0b..fcee60a7c37 100644 --- a/e2e/docs-e2e/package.json +++ b/e2e/docs-e2e/package.json @@ -1,7 +1,6 @@ { "name": "docs-e2e", "description": "", - "private": true, "author": "", "devDependencies": { "@playwright/test": "1.54.1", @@ -10,6 +9,7 @@ "keywords": [], "license": "ISC", "main": "index.js", + "private": true, "scripts": { "test": "pnpm exec playwright test --config=playwright.config.ts --project=chromium", "test-ui": "pnpm exec playwright test --config=playwright.config.ts --project=chromium --ui" diff --git a/e2e/qwik-react-e2e/package.json b/e2e/qwik-react-e2e/package.json index 71d5c96f4d9..ed24607458b 100644 --- a/e2e/qwik-react-e2e/package.json +++ b/e2e/qwik-react-e2e/package.json @@ -1,10 +1,6 @@ { "name": "qwik-react-test-app", "description": "Qwik react test app", - "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" - }, - "private": true, "devDependencies": { "@builder.io/qwik-react": "workspace:^", "@types/react": "18.3.3", @@ -12,6 +8,10 @@ "react": "18.3.1", "react-dom": "18.3.1" }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "private": true, "scripts": { "build": "qwik build", "build.client": "vite build", diff --git a/package.json b/package.json index 46cff897ba7..b6677561c79 100644 --- a/package.json +++ b/package.json @@ -210,8 +210,8 @@ "deps": "corepack pnpm upgrade -i -r --latest && syncpack fix-mismatches && corepack pnpm dedupe", "docs.dev": "pnpm -C packages/docs build.repl-sw && pnpm -C packages/docs dev", "docs.preview": "pnpm -C packages/docs preview", - "docs.sync": "node --require ./scripts/runBefore.ts scripts/docs_sync/index.ts && pnpm fmt", "docs.showcase": "pnpm -C packages/docs run build.showcase", + "docs.sync": "node --require ./scripts/runBefore.ts scripts/docs_sync/index.ts && pnpm fmt", "eslint.update": "node --require ./scripts/runBefore.ts scripts/eslint-docs.ts", "fmt": "pnpm prettier.fix && pnpm syncpack format", "fmt.staged": "pretty-quick --staged", @@ -244,9 +244,9 @@ "test.e2e.firefox": "playwright test starters --browser=firefox --config starters/playwright.config.ts", "test.e2e.integrations.chromium": "playwright test e2e/adapters-e2e/tests --project=chromium --config e2e/adapters-e2e/playwright.config.ts", "test.e2e.integrations.webkit": "playwright test e2e/adapters-e2e/tests --project=webkit --config e2e/adapters-e2e/playwright.config.ts", - "test.e2e.webkit": "playwright test starters --browser=webkit --config starters/playwright.config.ts", "test.e2e.qwik-react.chromium": "playwright test e2e/qwik-react-e2e/tests --project=chromium --config e2e/qwik-react-e2e/playwright.config.ts", "test.e2e.qwik-react.webkit": "playwright test e2e/qwik-react-e2e/tests --project=webkit --config e2e/qwik-react-e2e/playwright.config.ts", + "test.e2e.webkit": "playwright test starters --browser=webkit --config starters/playwright.config.ts", "test.rust": "make test", "test.rust.update": "make test-update", "test.unit": "vitest packages", diff --git a/packages/docs/package.json b/packages/docs/package.json index cf1809f79cf..8c18a3a0b31 100644 --- a/packages/docs/package.json +++ b/packages/docs/package.json @@ -40,10 +40,10 @@ "leaflet": "1.9.4", "magic-string": "0.30.17", "openai": "3.3.0", + "playwright": "1.58.2", "prettier": "3.6.2", "prism-themes": "1.9.0", "prismjs": "1.30.0", - "playwright": "1.58.2", "qwik-image": "0.0.16", "react": "18.3.1", "react-dom": "18.3.1", diff --git a/packages/docs/src/routes/api/qwik-city/api.json b/packages/docs/src/routes/api/qwik-city/api.json index 646b20049b0..03a2b493b81 100644 --- a/packages/docs/src/routes/api/qwik-city/api.json +++ b/packages/docs/src/routes/api/qwik-city/api.json @@ -306,7 +306,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type GetValidatorInputType = VALIDATOR extends ValibotDataValidator ? v.InferInput : VALIDATOR extends ZodDataValidator ? z.input : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", + "content": "```typescript\nexport type GetValidatorInputType = VALIDATOR extends StandardSchemaDataValidator ? StandardSchemaV1.InferInput : VALIDATOR extends ValibotDataValidator ? v.InferInput : VALIDATOR extends ZodDataValidator ? z.input : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.getvalidatorinputtype.md" }, @@ -320,7 +320,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type GetValidatorOutputType = VALIDATOR extends ValibotDataValidator ? v.InferOutput : VALIDATOR extends ZodDataValidator ? z.output : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", + "content": "```typescript\nexport type GetValidatorOutputType = VALIDATOR extends StandardSchemaDataValidator ? StandardSchemaV1.InferOutput : VALIDATOR extends ValibotDataValidator ? v.InferOutput : VALIDATOR extends ZodDataValidator ? z.output : never;\n```\n**References:** [TypedDataValidator](#typeddatavalidator)", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.getvalidatoroutputtype.md" }, @@ -828,6 +828,48 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/sw-component.tsx", "mdFile": "qwik-city.serviceworkerregister.md" }, + { + "name": "standardSchema$", + "id": "standardschema_", + "hierarchy": [ + { + "name": "standardSchema$", + "id": "standardschema_" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n\n\n```typescript\nstandardSchema$: StandardSchemaConstructor\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts", + "mdFile": "qwik-city.standardschema_.md" + }, + { + "name": "StandardSchemaConstructor", + "id": "standardschemaconstructor", + "hierarchy": [ + { + "name": "StandardSchemaConstructor", + "id": "standardschemaconstructor" + } + ], + "kind": "TypeAlias", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n\n\n```typescript\nexport type StandardSchemaConstructor = {\n (schema: T): StandardSchemaDataValidator;\n (schema: (ev: RequestEvent) => T): StandardSchemaDataValidator;\n};\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", + "mdFile": "qwik-city.standardschemaconstructor.md" + }, + { + "name": "standardSchemaQrl", + "id": "standardschemaqrl", + "hierarchy": [ + { + "name": "standardSchemaQrl", + "id": "standardschemaqrl" + } + ], + "kind": "Variable", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n\n\n```typescript\nstandardSchemaQrl: StandardSchemaConstructorQRL\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts", + "mdFile": "qwik-city.standardschemaqrl.md" + }, { "name": "StaticGenerate", "id": "staticgenerate", @@ -880,7 +922,7 @@ } ], "kind": "TypeAlias", - "content": "```typescript\nexport type TypedDataValidator = ValibotDataValidator | ZodDataValidator;\n```", + "content": "```typescript\nexport type TypedDataValidator = ValibotDataValidator | ZodDataValidator | StandardSchemaDataValidator;\n```", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", "mdFile": "qwik-city.typeddatavalidator.md" }, @@ -968,6 +1010,20 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts", "mdFile": "qwik-city.valibot_.md" }, + { + "name": "ValibotConstructor", + "id": "valibotconstructor", + "hierarchy": [ + { + "name": "ValibotConstructor", + "id": "valibotconstructor" + } + ], + "kind": "TypeAlias", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n\n\n```typescript\nexport type ValibotConstructor = {\n (schema: T): ValibotDataValidator;\n (schema: (ev: RequestEvent) => T): ValibotDataValidator;\n};\n```", + "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts", + "mdFile": "qwik-city.valibotconstructor.md" + }, { "name": "valibotQrl", "id": "valibotqrl", diff --git a/packages/docs/src/routes/api/qwik-city/index.mdx b/packages/docs/src/routes/api/qwik-city/index.mdx index cae49fdb5f6..f81d6c14bcc 100644 --- a/packages/docs/src/routes/api/qwik-city/index.mdx +++ b/packages/docs/src/routes/api/qwik-city/index.mdx @@ -1401,11 +1401,13 @@ RequestEvent \| undefined ```typescript export type GetValidatorInputType = - VALIDATOR extends ValibotDataValidator - ? v.InferInput - : VALIDATOR extends ZodDataValidator - ? z.input - : never; + VALIDATOR extends StandardSchemaDataValidator + ? StandardSchemaV1.InferInput + : VALIDATOR extends ValibotDataValidator + ? v.InferInput + : VALIDATOR extends ZodDataValidator + ? z.input + : never; ``` **References:** [TypedDataValidator](#typeddatavalidator) @@ -1416,11 +1418,13 @@ export type GetValidatorInputType = ```typescript export type GetValidatorOutputType = - VALIDATOR extends ValibotDataValidator - ? v.InferOutput - : VALIDATOR extends ZodDataValidator - ? z.output - : never; + VALIDATOR extends StandardSchemaDataValidator + ? StandardSchemaV1.InferOutput + : VALIDATOR extends ValibotDataValidator + ? v.InferOutput + : VALIDATOR extends ZodDataValidator + ? z.output + : never; ``` **References:** [TypedDataValidator](#typeddatavalidator) @@ -2489,6 +2493,41 @@ JSXOutput [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/sw-component.tsx) +

standardSchema$

+ +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +```typescript +standardSchema$: StandardSchemaConstructor; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts) + +

StandardSchemaConstructor

+ +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +```typescript +export type StandardSchemaConstructor = { + (schema: T): StandardSchemaDataValidator; + ( + schema: (ev: RequestEvent) => T, + ): StandardSchemaDataValidator; +}; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) + +

standardSchemaQrl

+ +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +```typescript +standardSchemaQrl: StandardSchemaConstructorQRL; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts) +

StaticGenerate

```typescript @@ -2556,7 +2595,10 @@ export type StrictUnion = Prettify>;

TypedDataValidator

```typescript -export type TypedDataValidator = ValibotDataValidator | ZodDataValidator; +export type TypedDataValidator = + | ValibotDataValidator + | ZodDataValidator + | StandardSchemaDataValidator; ``` [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) @@ -2681,6 +2723,23 @@ valibot$: ValibotConstructor; [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/server-functions.ts) +

ValibotConstructor

+ +> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. + +```typescript +export type ValibotConstructor = { + ( + schema: T, + ): ValibotDataValidator; + ( + schema: (ev: RequestEvent) => T, + ): ValibotDataValidator; +}; +``` + +[Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik-city/src/runtime/src/types.ts) +

valibotQrl

> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. diff --git a/packages/docs/src/routes/api/qwik-optimizer/api.json b/packages/docs/src/routes/api/qwik-optimizer/api.json index e91ee96c26a..f32d918c4a5 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/api.json +++ b/packages/docs/src/routes/api/qwik-optimizer/api.json @@ -147,7 +147,7 @@ } ], "kind": "Enum", - "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nUse `__EXPERIMENTAL__.x` to check if feature `x` is enabled. It will be replaced with `true` or `false` via an exact string replacement.\n\nAdd experimental features to this enum definition.\n\n\n```typescript\nexport declare enum ExperimentalFeatures \n```\n\n\n\n\n\n\n\n
\n\nMember\n\n\n\n\nValue\n\n\n\n\nDescription\n\n\n
\n\nenableRequestRewrite\n\n\n\n\n`\"enableRequestRewrite\"`\n\n\n\n\n**_(ALPHA)_** Enable request.rewrite()\n\n\n
\n\nnoSPA\n\n\n\n\n`\"noSPA\"`\n\n\n\n\n**_(ALPHA)_** Disable SPA navigation handler in Qwik City\n\n\n
\n\npreventNavigate\n\n\n\n\n`\"preventNavigate\"`\n\n\n\n\n**_(ALPHA)_** Enable the usePreventNavigate hook\n\n\n
\n\nvalibot\n\n\n\n\n`\"valibot\"`\n\n\n\n\n**_(ALPHA)_** Enable the Valibot form validation\n\n\n
", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\nUse `__EXPERIMENTAL__.x` to check if feature `x` is enabled. It will be replaced with `true` or `false` via an exact string replacement.\n\nAdd experimental features to this enum definition.\n\n\n```typescript\nexport declare enum ExperimentalFeatures \n```\n\n\n\n\n\n\n\n\n
\n\nMember\n\n\n\n\nValue\n\n\n\n\nDescription\n\n\n
\n\nenableRequestRewrite\n\n\n\n\n`\"enableRequestRewrite\"`\n\n\n\n\n**_(ALPHA)_** Enable request.rewrite()\n\n\n
\n\nnoSPA\n\n\n\n\n`\"noSPA\"`\n\n\n\n\n**_(ALPHA)_** Disable SPA navigation handler in Qwik City\n\n\n
\n\npreventNavigate\n\n\n\n\n`\"preventNavigate\"`\n\n\n\n\n**_(ALPHA)_** Enable the usePreventNavigate hook\n\n\n
\n\nstandardSchema\n\n\n\n\n`\"standardSchema\"`\n\n\n\n\n**_(ALPHA)_** Enable form validation using Standard Schema\n\n\n
\n\nvalibot\n\n\n\n\n`\"valibot\"`\n\n\n\n\n**_(ALPHA)_** Enable the Valibot form validation\n\n\n
", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/plugins/plugin.ts", "mdFile": "qwik.experimentalfeatures.md" }, @@ -727,6 +727,23 @@ "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts", "mdFile": "qwik.sourcemapsoption.md" }, + { + "name": "standardSchema", + "id": "experimentalfeatures-standardschema", + "hierarchy": [ + { + "name": "ExperimentalFeatures", + "id": "experimentalfeatures-standardschema" + }, + { + "name": "standardSchema", + "id": "experimentalfeatures-standardschema" + } + ], + "kind": "EnumMember", + "content": "", + "mdFile": "qwik.experimentalfeatures.standardschema.md" + }, { "name": "symbolMapper", "id": "symbolmapper", diff --git a/packages/docs/src/routes/api/qwik-optimizer/index.mdx b/packages/docs/src/routes/api/qwik-optimizer/index.mdx index 364ac538023..df5ca62d671 100644 --- a/packages/docs/src/routes/api/qwik-optimizer/index.mdx +++ b/packages/docs/src/routes/api/qwik-optimizer/index.mdx @@ -413,6 +413,19 @@ preventNavigate +standardSchema + + + +`"standardSchema"` + + + +**_(ALPHA)_** Enable form validation using Standard Schema + + + + valibot @@ -3023,6 +3036,8 @@ export type SourceMapsOption = "external" | "inline" | undefined | null; [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/optimizer/src/types.ts) +

standardSchema

+

symbolMapper

> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment. diff --git a/packages/docs/src/routes/api/qwik/api.json b/packages/docs/src/routes/api/qwik/api.json index 06806a18287..f22b23f7f80 100644 --- a/packages/docs/src/routes/api/qwik/api.json +++ b/packages/docs/src/routes/api/qwik/api.json @@ -1774,7 +1774,7 @@ } ], "kind": "Function", - "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n\n\n\n
\n\n**Returns:**\n\n[JSXNode](#jsxnode)<'script'>", + "content": "> This API is provided as an alpha preview for developers and may change based on feedback that we receive. Do not use this API in a production environment.\n> \n\n> Warning: This API is now obsolete.\n> \n> This is no longer needed as the preloading happens automatically in qrl-class.ts. Leave this in your app for a while so it uninstalls existing service workers, but don't use it for new projects.\n> \n\n\n```typescript\nPrefetchServiceWorker: (opts: {\n base?: string;\n scope?: string;\n path?: string;\n verbose?: boolean;\n fetchBundleGraph?: boolean;\n nonce?: string;\n}) => JSXNode<'script'>\n```\n\n\n\n\n
\n\nParameter\n\n\n\n\nType\n\n\n\n\nDescription\n\n\n
\n\nopts\n\n\n\n\n{ base?: string; scope?: string; path?: string; verbose?: boolean; fetchBundleGraph?: boolean; nonce?: string; }\n\n\n\n\n\n
\n\n**Returns:**\n\nJSXNode<'script'>", "editUrl": "https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts", "mdFile": "qwik.prefetchserviceworker.md" }, diff --git a/packages/docs/src/routes/api/qwik/index.mdx b/packages/docs/src/routes/api/qwik/index.mdx index 745c3223f2c..b7a4d867c7f 100644 --- a/packages/docs/src/routes/api/qwik/index.mdx +++ b/packages/docs/src/routes/api/qwik/index.mdx @@ -3667,7 +3667,7 @@ opts **Returns:** -[JSXNode](#jsxnode)<'script'> +JSXNode<'script'> [Edit this section](https://github.com/QwikDev/qwik/tree/main/packages/qwik/src/core/components/prefetch.ts) diff --git a/packages/qwik-city/package.json b/packages/qwik-city/package.json index bd4cedc0a13..3de9fa663dd 100644 --- a/packages/qwik-city/package.json +++ b/packages/qwik-city/package.json @@ -5,6 +5,7 @@ "bugs": "https://github.com/QwikDev/qwik/issues", "dependencies": { "@mdx-js/mdx": "^3.1.1", + "@standard-schema/spec": "^1.1.0", "@types/mdx": "^2.0.13", "source-map": "^0.7.6", "svgo": "^3.3.3", diff --git a/packages/qwik-city/src/runtime/src/index.ts b/packages/qwik-city/src/runtime/src/index.ts index c2ed948968e..3fbcfabd4b5 100644 --- a/packages/qwik-city/src/runtime/src/index.ts +++ b/packages/qwik-city/src/runtime/src/index.ts @@ -42,6 +42,8 @@ export type { StaticGenerateHandler, ValidatorErrorKeyDotNotation, ValidatorErrorType, + StandardSchemaConstructor, + ValibotConstructor, ZodConstructor, } from './types'; @@ -64,6 +66,7 @@ export { globalAction$, globalActionQrl } from './server-functions'; export { routeLoader$, routeLoaderQrl } from './server-functions'; export { server$, serverQrl } from './server-functions'; export { getRequestEvent } from './server-functions'; +export { standardSchema$, standardSchemaQrl } from './server-functions'; export { valibot$, valibotQrl } from './server-functions'; export { zod$, zodQrl } from './server-functions'; export { validator$, validatorQrl } from './server-functions'; diff --git a/packages/qwik-city/src/runtime/src/qwik-city.runtime.api.md b/packages/qwik-city/src/runtime/src/qwik-city.runtime.api.md index 89ba23a6e96..6d700d90871 100644 --- a/packages/qwik-city/src/runtime/src/qwik-city.runtime.api.md +++ b/packages/qwik-city/src/runtime/src/qwik-city.runtime.api.md @@ -23,6 +23,7 @@ import { RequestEventCommon } from '@builder.io/qwik-city/middleware/request-han import { RequestEventLoader } from '@builder.io/qwik-city/middleware/request-handler'; import { RequestHandler } from '@builder.io/qwik-city/middleware/request-handler'; import type { ResolveSyncValue } from '@builder.io/qwik-city/middleware/request-handler'; +import type { StandardSchemaV1 } from '@standard-schema/spec'; import type * as v from 'valibot'; import { ValueOrPromise } from '@builder.io/qwik'; import { z } from 'zod'; @@ -243,14 +244,15 @@ export interface FormSubmitSuccessDetail { // @public (undocumented) export const getRequestEvent: () => RequestEvent | undefined; +// Warning: (ae-forgotten-export) The symbol "StandardSchemaDataValidator" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ValibotDataValidator" needs to be exported by the entry point index.d.ts // Warning: (ae-forgotten-export) The symbol "ZodDataValidator" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export type GetValidatorInputType = VALIDATOR extends ValibotDataValidator ? v.InferInput : VALIDATOR extends ZodDataValidator ? z_2.input : never; +export type GetValidatorInputType = VALIDATOR extends StandardSchemaDataValidator ? StandardSchemaV1.InferInput : VALIDATOR extends ValibotDataValidator ? v.InferInput : VALIDATOR extends ZodDataValidator ? z_2.input : never; // @public (undocumented) -export type GetValidatorOutputType = VALIDATOR extends ValibotDataValidator ? v.InferOutput : VALIDATOR extends ZodDataValidator ? z_2.output : never; +export type GetValidatorOutputType = VALIDATOR extends StandardSchemaDataValidator ? StandardSchemaV1.InferOutput : VALIDATOR extends ValibotDataValidator ? v.InferOutput : VALIDATOR extends ZodDataValidator ? z_2.output : never; // @public (undocumented) export type GetValidatorType = GetValidatorOutputType; @@ -466,6 +468,20 @@ export const ServiceWorkerRegister: (props: { nonce?: string; }) => JSXOutput; +// @alpha (undocumented) +export const standardSchema$: StandardSchemaConstructor; + +// @alpha (undocumented) +export type StandardSchemaConstructor = { + (schema: T): StandardSchemaDataValidator; + (schema: (ev: RequestEvent) => T): StandardSchemaDataValidator; +}; + +// Warning: (ae-forgotten-export) The symbol "StandardSchemaConstructorQRL" needs to be exported by the entry point index.d.ts +// +// @alpha (undocumented) +export const standardSchemaQrl: StandardSchemaConstructorQRL; + // @public (undocumented) export interface StaticGenerate { // (undocumented) @@ -484,7 +500,7 @@ export type StaticGenerateHandler = ({ env, }: { export type StrictUnion = Prettify>; // @public (undocumented) -export type TypedDataValidator = ValibotDataValidator | ZodDataValidator; +export type TypedDataValidator = ValibotDataValidator | ZodDataValidator | StandardSchemaDataValidator; // Warning: (ae-forgotten-export) The symbol "ContentState" needs to be exported by the entry point index.d.ts // @@ -508,11 +524,15 @@ export const usePreventNavigate$: (qrl: PreventNavigateCallback) => void; // @internal export const usePreventNavigateQrl: (fn: QRL) => void; -// Warning: (ae-forgotten-export) The symbol "ValibotConstructor" needs to be exported by the entry point index.d.ts -// // @alpha (undocumented) export const valibot$: ValibotConstructor; +// @alpha (undocumented) +export type ValibotConstructor = { + (schema: T): ValibotDataValidator; + (schema: (ev: RequestEvent) => T): ValibotDataValidator; +}; + // Warning: (ae-forgotten-export) The symbol "ValibotConstructorQRL" needs to be exported by the entry point index.d.ts // // @alpha (undocumented) diff --git a/packages/qwik-city/src/runtime/src/server-functions.ts b/packages/qwik-city/src/runtime/src/server-functions.ts index 9d0d47aae9c..a63d3912759 100644 --- a/packages/qwik-city/src/runtime/src/server-functions.ts +++ b/packages/qwik-city/src/runtime/src/server-functions.ts @@ -38,6 +38,9 @@ import type { ServerConfig, ServerFunction, ServerQRL, + StandardSchemaConstructorQRL, + StandardSchemaConstructor, + StandardSchemaDataValidator, ValidatorConstructor, ValidatorConstructorQRL, ValidatorReturn, @@ -53,6 +56,7 @@ import { useAction, useLocation, useQwikCityEnv } from './use-functions'; import { isDev, isServer } from '@builder.io/qwik'; import type { FormSubmitCompletedDetail } from './form-component'; +import type { StandardSchemaV1 } from '@standard-schema/spec'; /** @public */ export const getRequestEvent = (): RequestEvent | undefined => { @@ -234,6 +238,70 @@ export const validatorQrl = (( /** @public */ export const validator$: ValidatorConstructor = /*#__PURE__*/ implicit$FirstArg(validatorQrl); +const flattenStandardSchemaIssues = (issues: readonly StandardSchemaV1.Issue[]) => { + const fieldErrors: Record = {}; + let formErrors = []; + type Segment = StandardSchemaV1.Issue['path'] extends readonly (infer S)[] | undefined + ? S + : never; + const segmentKey = (segment: Segment): PropertyKey => + typeof segment === 'object' && segment !== null ? segment.key : segment; + for (const issue of issues) { + if (!issue.path || issue.path.length === 0 || issue.path[0] === '') + formErrors.push(issue.message); + if (issue.path) { + fieldErrors[issue.path.map((seg) => String(segmentKey(seg))).join('.')] = issue.message; + } + } + return { formErrors, fieldErrors }; +}; + +/** @alpha */ +export const standardSchemaQrl: StandardSchemaConstructorQRL = ( + qrl: QRL StandardSchemaV1)> +): StandardSchemaDataValidator => { + if (!__EXPERIMENTAL__.standardSchema) { + throw new Error( + 'Standard Schema validation is an experimental feature and is not enabled. Please enable the feature flag by adding `experimental: ["standardSchema"]` to your qwikVite plugin options.' + ); + } + if (isServer) { + return { + __brand: 'standard-schema', + async validate(ev, inputData) { + const schema: StandardSchemaV1 = await qrl + .resolve() + .then((obj) => (!('~standard' in obj) && typeof obj === 'function' ? obj(ev) : obj)); + const data = inputData ?? (await ev.parseBody()); + const result = await schema['~standard'].validate(data); + if (result.issues) { + if (isDev) { + console.error( + `ERROR: Standard Schema (${schema['~standard'].vendor}) validation failed`, + result.issues + ); + } + return { + success: false, + status: 400, + error: flattenStandardSchemaIssues(result.issues) as never, + }; + } else { + return { + success: true, + data: result.value, + }; + } + }, + }; + } + return undefined as never; +}; + +/** @alpha */ +export const standardSchema$: StandardSchemaConstructor = + /*#__PURE__*/ implicit$FirstArg(standardSchemaQrl); + const flattenValibotIssues = (issues: v.GenericIssue[]) => { return issues.reduce>((acc, issue) => { if (issue.path) { diff --git a/packages/qwik-city/src/runtime/src/types.ts b/packages/qwik-city/src/runtime/src/types.ts index d5f0af33201..acba0d6a9b2 100644 --- a/packages/qwik-city/src/runtime/src/types.ts +++ b/packages/qwik-city/src/runtime/src/types.ts @@ -15,6 +15,7 @@ import type { ResolveSyncValue, EnvGetter, } from '@builder.io/qwik-city/middleware/request-handler'; +import type { StandardSchemaV1 } from '@standard-schema/spec'; import type * as v from 'valibot'; import type * as z from 'zod'; @@ -368,19 +369,23 @@ export type JSONObject = { [x: string]: JSONValue }; /** @public */ export type GetValidatorInputType = - VALIDATOR extends ValibotDataValidator - ? v.InferInput - : VALIDATOR extends ZodDataValidator - ? z.input - : never; + VALIDATOR extends StandardSchemaDataValidator + ? StandardSchemaV1.InferInput + : VALIDATOR extends ValibotDataValidator + ? v.InferInput + : VALIDATOR extends ZodDataValidator + ? z.input + : never; /** @public */ export type GetValidatorOutputType = - VALIDATOR extends ValibotDataValidator - ? v.InferOutput - : VALIDATOR extends ZodDataValidator - ? z.output - : never; + VALIDATOR extends StandardSchemaDataValidator + ? StandardSchemaV1.InferOutput + : VALIDATOR extends ValibotDataValidator + ? v.InferOutput + : VALIDATOR extends ZodDataValidator + ? z.output + : never; /** @public */ export type GetValidatorType = @@ -831,6 +836,29 @@ export type ValidatorConstructorQRL = { ): T extends ValidatorReturnFail ? DataValidator : DataValidator; }; +/** @alpha */ +export type StandardSchemaDataValidator = { + readonly __brand: 'standard-schema'; + validate( + ev: RequestEvent, + data: unknown + ): Promise>>>; +}; + +/** @alpha */ +export type StandardSchemaConstructor = { + (schema: T): StandardSchemaDataValidator; + (schema: (ev: RequestEvent) => T): StandardSchemaDataValidator; +}; + +/** @alpha */ +export type StandardSchemaConstructorQRL = { + (schema: QRL): StandardSchemaDataValidator; + ( + schema: QRL<(ev: RequestEvent) => T> + ): StandardSchemaDataValidator; +}; + /** @alpha */ export type ValibotDataValidator< T extends v.GenericSchema | v.GenericSchemaAsync = v.GenericSchema | v.GenericSchemaAsync, @@ -888,7 +916,10 @@ export type ZodConstructorQRL = { }; /** @public */ -export type TypedDataValidator = ValibotDataValidator | ZodDataValidator; +export type TypedDataValidator = + | ValibotDataValidator + | ZodDataValidator + | StandardSchemaDataValidator; /** @public */ export interface ServerConfig { diff --git a/packages/qwik/package.json b/packages/qwik/package.json index 80188162d72..9d3b58b53e2 100644 --- a/packages/qwik/package.json +++ b/packages/qwik/package.json @@ -47,8 +47,8 @@ "import": { "development": "./dist/core.mjs", "production": "./dist/core.prod.mjs", - "min": "./dist/core.min.mjs", - "default": "./dist/core.prod.mjs" + "default": "./dist/core.prod.mjs", + "min": "./dist/core.min.mjs" }, "require": { "development": "./dist/core.cjs", @@ -64,8 +64,8 @@ "import": { "development": "./dist/core.mjs", "production": "./dist/core.prod.mjs", - "min": "./dist/core.min.mjs", - "default": "./dist/core.prod.mjs" + "default": "./dist/core.prod.mjs", + "min": "./dist/core.min.mjs" }, "require": { "development": "./dist/core.cjs", @@ -78,8 +78,8 @@ "import": { "development": "./dist/core.mjs", "production": "./dist/core.prod.mjs", - "min": "./dist/core.min.mjs", - "default": "./dist/core.mjs" + "default": "./dist/core.mjs", + "min": "./dist/core.min.mjs" }, "require": { "development": "./dist/core.cjs", diff --git a/packages/qwik/src/optimizer/src/plugins/plugin.ts b/packages/qwik/src/optimizer/src/plugins/plugin.ts index 5172f795dd9..b6f80215e00 100644 --- a/packages/qwik/src/optimizer/src/plugins/plugin.ts +++ b/packages/qwik/src/optimizer/src/plugins/plugin.ts @@ -45,6 +45,8 @@ const SERVER_STRIP_CTX_NAME = [ 'action$', 'loader$', 'zod$', + 'valibot$', + 'standardSchema$', 'validator$', 'globalAction$', ]; @@ -70,6 +72,8 @@ export enum ExperimentalFeatures { preventNavigate = 'preventNavigate', /** Enable the Valibot form validation */ valibot = 'valibot', + /** Enable form validation using Standard Schema */ + standardSchema = 'standardSchema', /** Disable SPA navigation handler in Qwik City */ noSPA = 'noSPA', /** Enable request.rewrite() */ diff --git a/packages/qwik/src/optimizer/src/qwik.optimizer.api.md b/packages/qwik/src/optimizer/src/qwik.optimizer.api.md index 6030200d353..b87d8216d9d 100644 --- a/packages/qwik/src/optimizer/src/qwik.optimizer.api.md +++ b/packages/qwik/src/optimizer/src/qwik.optimizer.api.md @@ -55,6 +55,7 @@ export enum ExperimentalFeatures { enableRequestRewrite = "enableRequestRewrite", noSPA = "noSPA", preventNavigate = "preventNavigate", + standardSchema = "standardSchema", valibot = "valibot" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a6290aea0d9..d97376d3659 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -643,6 +643,9 @@ importers: '@mdx-js/mdx': specifier: ^3.1.1 version: 3.1.1 + '@standard-schema/spec': + specifier: ^1.1.0 + version: 1.1.0 '@types/mdx': specifier: ^2.0.13 version: 2.0.13 @@ -3455,6 +3458,9 @@ packages: resolution: {integrity: sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==} engines: {node: '>=12'} + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + '@supabase/auth-js@2.71.1': resolution: {integrity: sha512-mMIQHBRc+SKpZFRB2qtupuzulaUhFYupNyxqDj5Jp/LyPvcWvjaJzZzObv6URtL/O6lPxkanASnotGtNpS3H2Q==} @@ -12921,6 +12927,8 @@ snapshots: dependencies: escape-string-regexp: 5.0.0 + '@standard-schema/spec@1.1.0': {} + '@supabase/auth-js@2.71.1': dependencies: '@supabase/node-fetch': 2.6.15