diff --git a/biome.json b/biome.json index 2cd8a697..af2ee6d7 100644 --- a/biome.json +++ b/biome.json @@ -1,6 +1,6 @@ { - "root": true, "$schema": "https://biomejs.dev/schemas/2.1.3/schema.json", + "root": true, "assist": { "actions": { "source": { "organizeImports": "on" } } }, "formatter": { "indentStyle": "space", @@ -31,4 +31,4 @@ } } } -} \ No newline at end of file +} diff --git a/package.json b/package.json index deadc3c7..533bb823 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "standard-schema", - "description": "A standard interface for TypeScript schema validation libraries", + "description": "A family of specs for interoperable TypeScript", "version": "0.0.0", "license": "MIT", "author": "Colin McDonnell", diff --git a/packages/examples/json-implement.ts b/packages/examples/json-implement.ts new file mode 100644 index 00000000..383ae19a --- /dev/null +++ b/packages/examples/json-implement.ts @@ -0,0 +1,74 @@ +/** + * This example shows how to implement the Standard JSON Schema interface. + * It demonstrates creating schemas that support both validation and JSON Schema generation. + */ + +import type { + StandardJSONSchemaV1, + StandardSchemaV1, +} from "@standard-schema/spec"; + +interface CombinedProps + extends StandardSchemaV1.Props, + StandardJSONSchemaV1.Props {} + +/** + * An interface that combines StandardJSONSchema and StandardSchema. + * */ +interface StandardSchemaWithJSONSchema { + "~standard": CombinedProps; +} + +interface MySchema extends StandardSchemaWithJSONSchema { + type: "string"; +} + +export function stringWithJSON(): MySchema { + return { + type: "string", + "~standard": { + version: 1, + vendor: "example-lib", + validate(value) { + return typeof value === "string" + ? { value } + : { issues: [{ message: "Invalid string", path: [] }] }; + }, + jsonSchema: { + input(params) { + const schema: Record = { + type: "string", + }; + + // Add schema version based on target + if (params.target === "draft-2020-12") { + schema.$schema = "https://json-schema.org/draft/2020-12/schema"; + } else if (params.target === "draft-07") { + schema.$schema = "http://json-schema.org/draft-07/schema#"; + } else { + throw new Error(`Unsupported target: ${params.target}`); + } + + return schema; + }, + output(params) { + // input and output are the same in this example + return this.input(params); + }, + }, + }, + }; +} + +// usage example +const stringSchema = stringWithJSON(); + +stringSchema["~standard"].jsonSchema.input({ + target: "draft-2020-12", +}); +// => { $schema: "https://json-schema.org/draft/2020-12/schema", type: "string" } + +stringSchema["~standard"].jsonSchema.input({ + target: "draft-07", +}); +// => { $schema: "http://json-schema.org/draft-07/schema#", type: "string" } diff --git a/packages/examples/json-integrate.ts b/packages/examples/json-integrate.ts new file mode 100644 index 00000000..40e527b1 --- /dev/null +++ b/packages/examples/json-integrate.ts @@ -0,0 +1,19 @@ +import type { StandardJSONSchemaV1 } from "@standard-schema/spec"; + +// Function that accepts any compliant `StandardJSONSchemaV1` +// and converts it to a JSON Schema. +export function acceptSchema(schema: StandardJSONSchemaV1): unknown { + // do stuff, e.g. + return schema["~standard"].jsonSchema.input({ + target: "draft-2020-12", + }); +} + +export function parseData( + schema: T, + data: StandardJSONSchemaV1.InferInput, // extract input type +) { + // @ts-expect-error - replace doStuff with your own logic + const result = doStuff(schema, data); + return result as StandardJSONSchemaV1.InferOutput; // extract output type +} diff --git a/packages/examples/package.json b/packages/examples/package.json index d09ec4bb..9f47d8bf 100644 --- a/packages/examples/package.json +++ b/packages/examples/package.json @@ -5,7 +5,8 @@ "scripts": { "lint": "pnpm biome lint .", "format": "pnpm biome format --write .", - "check": "pnpm biome check ." + "check": "pnpm biome check .", + "build": "tsup" }, "devDependencies": { "@standard-schema/spec": "workspace:*", diff --git a/packages/examples/tsconfig.json b/packages/examples/tsconfig.json index eadec2e7..df0b3794 100644 --- a/packages/examples/tsconfig.json +++ b/packages/examples/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowImportingTsExtensions": true, + "customConditions": ["standard-schema-spec"], "declaration": true, "exactOptionalPropertyTypes": true, "isolatedDeclarations": true, diff --git a/packages/spec/README.md b/packages/spec/README.md index b2d6b329..f9813ffa 100644 --- a/packages/spec/README.md +++ b/packages/spec/README.md @@ -3,7 +3,7 @@
Standard Schema

- A common interface for TypeScript validation libraries + A family of specs for interoperable TypeScript
standardschema.dev

@@ -11,40 +11,74 @@ -Standard Schema is a common interface designed to be implemented by JavaScript and TypeScript schema libraries. +The Standard Schema project is a set of interfaces that standardize the provision and consumption of shared functionality in the TypeScript ecosystem. -The goal is to make it easier for ecosystem tools to accept user-defined type validators, without needing to write custom logic or adapters for each supported library. And since Standard Schema is a specification, they can do so with no additional runtime dependencies. Integrate once, validate anywhere. +Its goal is to allow tools to accept a single input that includes all the types and capabilities they needβ€” no library-specific adapters, no extra dependencies. The result is an ecosystem that's fair for implementers, friendly for consumers, and open for end users. -## Who designed it? +## The specifications -The spec was designed by the creators of Zod, Valibot, and ArkType. Recent versions of these libraries already implement the spec (see the [full list of compatible libraries](#what-schema-libraries-implement-the-spec) below). +The specifications can be found below in their entirety. Libraries wishing to implement a spec can copy/paste the code block below into their codebase. They're also available at `@standard-schema/spec` on [npm](https://www.npmjs.com/package/@standard-schema/spec) and [JSR](https://jsr.io/@standard-schema/spec). -## The interface +```ts +// ######################### +// ### Standard Typed ### +// ######################### + +/** The Standard Typed interface. This is a base type extended by other specs. */ +export interface StandardTypedV1 { + /** The Standard properties. */ + readonly "~standard": StandardTypedV1.Props; +} + +export declare namespace StandardTypedV1 { + /** The Standard Typed properties interface. */ + export interface Props { + /** The version number of the standard. */ + readonly version: 1; + /** The vendor name of the schema library. */ + readonly vendor: string; + /** Inferred types associated with the schema. */ + readonly types?: Types | undefined; + } + + /** The Standard Typed types interface. */ + export interface Types { + /** The input type of the schema. */ + readonly input: Input; + /** The output type of the schema. */ + readonly output: Output; + } -The specification consists of a single TypeScript interface `StandardSchemaV1` to be implemented by any schema library wishing to be spec-compliant. + /** Infers the input type of a Standard Typed. */ + export type InferInput = NonNullable< + Schema["~standard"]["types"] + >["input"]; -This interface can be found below in its entirety. Libraries wishing to implement the spec can copy/paste the code block below into their codebase. It's also available at `@standard-schema/spec` on [npm](https://www.npmjs.com/package/@standard-schema/spec) and [JSR](https://jsr.io/@standard-schema/spec). There will be zero changes without a major version bump. + /** Infers the output type of a Standard Typed. */ + export type InferOutput = NonNullable< + Schema["~standard"]["types"] + >["output"]; +} + +// ########################## +// ### Standard Schema ### +// ########################## -```ts /** The Standard Schema interface. */ export interface StandardSchemaV1 { /** The Standard Schema properties. */ - readonly '~standard': StandardSchemaV1.Props; + readonly "~standard": StandardSchemaV1.Props; } export declare namespace StandardSchemaV1 { /** The Standard Schema properties interface. */ - export interface Props { - /** The version number of the standard. */ - readonly version: 1; - /** The vendor name of the schema library. */ - readonly vendor: string; + export interface Props + extends StandardTypedV1.Props { /** Validates unknown input values. */ readonly validate: ( - value: unknown + value: unknown, + options?: StandardSchemaV1.Options | undefined ) => Result | Promise>; - /** Inferred types associated with the schema. */ - readonly types?: Types | undefined; } /** The result interface of the validate function. */ @@ -54,10 +88,15 @@ export declare namespace StandardSchemaV1 { export interface SuccessResult { /** The typed output value. */ readonly value: Output; - /** The non-existent issues. */ + /** A falsy value for `issues` indicates success. */ readonly issues?: undefined; } + export interface Options { + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + /** The result interface if validation fails. */ export interface FailureResult { /** The issues of failed validation. */ @@ -78,259 +117,82 @@ export declare namespace StandardSchemaV1 { readonly key: PropertyKey; } - /** The Standard Schema types interface. */ - export interface Types { - /** The input type of the schema. */ - readonly input: Input; - /** The output type of the schema. */ - readonly output: Output; - } + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} - /** Infers the input type of a Standard Schema. */ - export type InferInput = NonNullable< - Schema['~standard']['types'] - >['input']; + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; - /** Infers the output type of a Standard Schema. */ - export type InferOutput = NonNullable< - Schema['~standard']['types'] - >['output']; + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; } -``` - -## Design goals - -The specification meets a few primary design objectives: - -- **Support runtime validation.** Given a Standard Schema compatible validator, you should be able to validate data with it (duh). Any validation errors should be presented in a standardized format. -- **Support static type inference.** For TypeScript libraries that do type inference, the specification provides a standard way for them to "advertise" their inferred type, so it can be extracted and used by external tools. -- **Minimal.** It should be easy for libraries to implement this spec in a few lines of code that call their existing functions/methods. -- **Avoid API conflicts.** The entire spec is tucked inside a single object property called `~standard`, which avoids potential naming conflicts with the API surface of existing libraries. -- **Do no harm to DX.** The `~standard` property is tilde-prefixed to [de-prioritize it in autocompletion](https://x.com/colinhacks/status/1816860780459073933). By contrast, an underscore-prefixed property would show up before properties/methods with alphanumeric names. - -## What schema libraries implement the spec? - -These are the libraries that have already implemented the Standard Schema interface. (If you maintain a library that implements the spec, [create a PR](https://github.com/standard-schema/standard-schema/compare) to add yourself!) - -| Implementer | Version(s) | Link | -| ---------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | -| [Zod](https://zod.dev) | 3.24.0+ | [PR](https://github.com/colinhacks/zod/pull/3850) | -| [Valibot](https://valibot.dev/) | v1.0+ | [PR](https://github.com/fabian-hiller/valibot/pull/845) | -| [ArkType](https://arktype.io/) | v2.0+ | [PR](https://github.com/arktypeio/arktype/pull/1194/files) | -| [Effect Schema](https://effect.website/docs/schema/introduction/) (⚠️ via adapter) | v3.13.0+ | [PR](https://github.com/Effect-TS/effect/pull/4359) | -| [Arri Schema](https://github.com/modiimedia/arri) | v0.71.0+ | [PR](https://github.com/modiimedia/arri/pull/130) | -| [Formgator](https://github.com/GauBen/formgator) | v0.1.0+ | [Commit](https://github.com/GauBen/formgator/commit/12c8a90) | -| [decoders](https://github.com/nvie/decoders) | v2.6.0+ | [PR](https://github.com/nvie/decoders/pull/1213) | -| [Sury](https://github.com/DZakh/sury) | v9.2.0+ | [PR](https://github.com/DZakh/sury/pull/105) | -| [Skunkteam Types](https://github.com/skunkteam/types) | v9.0.0+ | [PR](https://github.com/skunkteam/types/pull/108) | -| [DreamIt GraphQL-Std-Schema](https://github.com/dreamit-de/graphql-std-schema) | v0.1.0+ | [Commit](https://github.com/dreamit-de/graphql-std-schema/commit/c948d4f56ea72d57744976735bedcaea43d33ae1) | -| [ts.data.json](https://github.com/joanllenas/ts.data.json) | v2.3.0+ | [PR](https://github.com/joanllenas/ts.data.json/pull/35) | -| [quartet](https://github.com/whiteand/ts-quartet) | v11.0.3+ | [PR](https://github.com/whiteand/ts-quartet/pull/9/files) | -| [unhoax](https://sacdenoeuds.github.io/unhoax/) | v0.7.0+ | [Commit](https://github.com/SacDeNoeuds/unhoax/commit/4e7f2d71c98ba9e3e73f029e0af2e46f9980dcbe) | -| [protovalidate-es](https://github.com/bufbuild/protovalidate-es) | v0.5.0+ | [PR](https://github.com/bufbuild/protovalidate-es/pull/54) | -| [remult](https://remult.dev/docs/standard-schema) | v3.1.1+ | [PR](https://github.com/remult/remult/pull/777) | -| [stnl](https://github.com/re-utils/stnl?tab=readme-ov-file#standard-schema) | v1.1.9+ | [Commit](https://github.com/re-utils/stnl/commit/d748522920e6066de6cc5eefa9c93c38f5b0541a) | -| [yup](https://github.com/jquense/yup) | v1.7.0+ | [PR](https://github.com/jquense/yup/pull/2258) | -| [joi](https://github.com/hapijs/joi) | v18.0.0+ | [PR](https://github.com/hapijs/joi/pull/3080) | -| [typia](https://github.com/samchon/typia) | v9.2.0+ | [PR](https://github.com/samchon/typia/pull/1500) | -| [regle](https://github.com/victorgarciaesgi/regle) | v1.9.0+ | [PR](https://github.com/victorgarciaesgi/regle/pull/191) | -| [jsonv-ts](https://github.com/dswbx/jsonv-ts) | v0.3.0+ | [PR](https://github.com/dswbx/jsonv-ts/pull/7) | -| [Evolu](https://github.com/evoluhq/evolu) | v6.0.1+ | [PR](https://github.com/evoluhq/evolu/pull/605) | -| [zeed](https://github.com/holtwick/zeed) | v1.1.0+ | [Commit](https://github.com/holtwick/zeed/commit/05fb980df7ed0722f45a417bad8f1560195c7e1b) | - -## What tools / frameworks accept spec-compliant schemas? - -The following tools accept user-defined schemas conforming to the Standard Schema spec. (If you maintain a tool that supports Standard Schemas, [create a PR](https://github.com/standard-schema/standard-schema/compare) to add yourself!) - -| Integrator | Description | Link | -| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | -| [tRPC](https://trpc.io) | Move fast and break nothing, end-to-end typesafe APIs made easy | [PR](https://github.com/trpc/trpc/pull/6079) | -| [TanStack Form](https://tanstack.com/form) | Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit | [PR](https://github.com/TanStack/form/pull/1020) | -| [TanStack Router](https://tanstack.com/router/latest) | A fully type-safe React router with built-in data fetching, stale-while revalidate caching and first-class search-param APIs | [PR](https://github.com/TanStack/router/pull/2602) | -| [Hono Middleware](https://hono.dev) | Fast, lightweight server, built on Web Standards | [PR](https://github.com/honojs/middleware/pull/887) | -| [Elysia](https://elysiajs.com) | Ergonomic framework for humans | [PR](https://github.com/elysiajs/elysia/pull/1381) | -| [Qwik 🚧](https://qwik.dev/) | Instant-loading web apps, without effort | [PR](https://github.com/QwikDev/qwik/pull/7281) | -| [UploadThing](https://uploadthing.com/) | File uploads for modern web devs | [Docs](https://docs.uploadthing.com/file-routes#input) | -| [T3 Env](https://env.t3.gg/docs/introduction) | Framework agnostic validation for type-safe environment variables | [PR](https://github.com/t3-oss/t3-env/pull/299) | -| [OpenAuth](https://openauth.js.org/) | Universal, standards-based auth provider | [Docs](https://openauth.js.org/docs/#server) | -| [renoun](https://www.renoun.dev/) | The documentation toolkit for React | [Docs](https://www.renoun.dev/utilities/file-system#schema-validation) | -| [Formwerk](https://formwerk.dev/) | A Vue.js framework for building high-quality, accessible, delightful forms | [PR](https://github.com/formwerkjs/formwerk/pull/68) | -| [GQLoom](https://gqloom.dev/) | Weave GraphQL schema and resolvers using Standard Schema | [PR](https://github.com/modevol-com/gqloom/pull/7) | -| [Nuxt UI](https://ui.nuxt.com/) | A UI library for modern web apps, powered by Vue & Tailwind CSS | [PR](https://github.com/nuxt/ui/pull/2303) | -| [oRPC](https://orpc.unnoq.com/) | Typesafe APIs made simple πŸͺ„ | [PR](https://github.com/unnoq/orpc/pull/50) | -| [Regle](https://reglejs.dev/) | Type-safe model-based form validation library for Vue.js | [PR](https://github.com/victorgarciaesgi/regle/pull/46) | -| [upfetch](https://github.com/L-Blondy/up-fetch) | Tiny & composable fetch configuration tool with sensible defaults and built-in schema validation | [PR](https://github.com/L-Blondy/up-fetch/pull/11) | -| [rest-client](https://github.com/logione/rest-client) | Ultra-lightweight and easy-to-use http(s) client for node.js supporting JSON and streams with no external dependencies | [Docs](https://github.com/logione/rest-client) | -| [make-service](https://github.com/gustavoguichard/make-service) | A set of utilities to improve the DX of native `fetch` to better interact with external APIs | [PR](https://github.com/gustavoguichard/make-service/pull/57) | -| [call-api](https://github.com/zayne-labs/callapi) | A lightweight fetching library with retries, interceptors, request deduplication and more | [PR](https://github.com/zayne-labs/call-api/pull/131) | -| [cachified](https://github.com/epicweb-dev/cachified) | Use everything as a cache with type-safety (by Standard Schema), stale-while-revalidate, parallel-fetch deduplication, ... | [Docs](https://github.com/epicweb-dev/cachified?tab=readme-ov-file#type-safety-with-schema-libraries) | -| [React Hook Form](https://github.com/react-hook-form/react-hook-form) | React Hooks for form state management and validation (Web + React Native) | [PR](https://github.com/react-hook-form/resolvers/pull/738) | -| [Mage](https://github.com/deno-mage/server) | Build web applications with Deno and Preact | [Docs](https://deno-mage.com/validation) | -| [Better-fetch](https://github.com/bekacru/better-fetch) | Advanced fetch library for TypeScript with Zod validation, pre-defined routes, hooks, plugins and more | [Docs](https://better-fetch.vercel.app/) | -| [server-act](https://github.com/chungweileong94/server-act) | A simple React server action builder that provides input validation | [PR](https://github.com/chungweileong94/server-act/pull/37) | -| [xink](https://github.com/xinkjs/xink) | A filesystem API router, inspired by SvelteKit | [Docs](https://github.com/xinkjs/xink?tab=readme-ov-file#using-standard-schema) | -| [xsAI](https://github.com/moeru-ai/xsai) | Extra-small AI SDK | [Docs](https://xsai.js.org/docs/packages/tool) | -| [xsSchema](https://github.com/moeru-ai/xsai/blob/main/packages/xsschema) | Extra-small, Standard Schema-based alternative to typeschema | [Docs](https://xsai.js.org/docs/packages-top/xsschema) | -| [xsMCP](https://github.com/moeru-ai/xsmcp) | Extra-small MCP SDK | [Docs](https://xsmcp.js.org/docs/server/shared/tools) | -| [DreamIT GraphQL-Server](https://github.com/dreamit-de/graphql-server) | GraphQL server written in NodeJS/TypeScript | [PR](https://github.com/dreamit-de/graphql-server/pull/479) | -| [Astro Typesafe Routes](https://github.com/feelixe/astro-typesafe-routes) | An Astro integration for typesafe URL generation and routing | [PR](https://github.com/feelixe/astro-typesafe-routes/pull/52) | -| [Muppet](https://github.com/muppet-dev/muppet) | Build MCPs (Model Context Protocol) on top of Hono | [Repo](https://github.com/muppet-dev/muppet) | -| [ts-ics](https://github.com/Neuvernetzung/ts-ics) | Parse and generate iCalendar with TypeScript | [PR](https://github.com/Neuvernetzung/ts-ics/pull/148) | -| [kvdex](https://github.com/oliver-oloughlin/kvdex) | A high-level abstraction layer for Deno KV. Works in Deno, Bun, Node.js and the browser πŸ¦•πŸ—οΈ | [PR](https://github.com/oliver-oloughlin/kvdex/pull/271) | -| [FastMCP](https://github.com/punkpeye/fastmcp) | A TypeScript framework for building MCP servers capable of handling client sessions | [PR](https://github.com/punkpeye/fastmcp/pull/27) | -| [RVF](https://www.rvf-js.io/) | Easy and powerful form state management and validation for React | [PR](https://github.com/airjp73/rvf/pull/431) | -| [SchemQl](https://github.com/a2lix/schemql) | A lightweight TypeScript library that enhances your SQL workflow by combining raw SQL with targeted type safety and schema validation | [Repo](https://github.com/a2lix/schemql) | -| [data-double-dash](https://gitlab.com/alexandre-fernandez/data-double-dash) | Type-safe component framework for multi-page applications | [Docs](https://gitlab.com/alexandre-fernandez/data-double-dash) | -| [Slonik](https://github.com/gajus/slonik) | A Node.js PostgreSQL client with runtime and build time type safety, and composable SQL | [PR](https://github.com/gajus/slonik/pull/695) | -| [Nuxt Safe Runtime Config](https://github.com/onmax/nuxt-safe-runtime-config) | Validate Nuxt runtime config at build time using Zod, Valibot, ArkType, or any Standard Schema compatible library | [Docs](https://github.com/onmax/nuxt-safe-runtime-config) | -| [Content Collections](https://www.content-collections.dev/) | A library for converting content into type-safe data collections | [PR](https://github.com/sdorra/content-collections/pull/570) | -| [safeway](https://github.com/EskiMojo14/safeway) | A type-safe serialisation and validation wrapper for string storage APIs like `localStorage` and `sessionStorage` | [Docs](https://github.com/EskiMojo14/safeway/blob/main/README.md) | -| [True Myth](https://true-myth.js.org) | Safe, idiomatic null, error, and async code handling in TypeScript, with `Maybe`, `Result`, and `Task` types that are really nice | [Guide](https://true-myth.js.org/guide/understanding/standard-schema.html) | -| [@sugardarius/anzen](https://anzen.sugardarius.dev/) | A flexible, framework validation agnostic, type-safe factory for creating Next.JS App Router route handlers | [Docs](https://anzen.sugardarius.dev/) | -| [Conformal](https://github.com/marcomuser/conformal) | A framework-agnostic library for strongly typed FormData parsing | [Repo](https://github.com/marcomuser/conformal) | -| [next-safe-action](https://next-safe-action.dev) | Type safe and validated Server Actions in your Next.js project | [PR](https://github.com/TheEdoRan/next-safe-action/pull/324) | -| [Event Tracker](https://event-tracker.offlegacy.org/en) | A library for event tracking in React applications | [Docs](https://event-tracker.offlegacy.org/en/docs/data-type-validation) | -| [zod-jsonrpc](https://github.com/danscan/zod-jsonrpc) | Type-safe JSON-RPC 2.0 clients and servers | [Docs](https://github.com/danscan/zod-jsonrpc) | -| [xignal](https://github.com/flamrdevs/xignal) | signals state management for real | [Repo](https://github.com/flamrdevs/xignal) | -| [svelte-jsonschema-form](https://github.com/x0k/svelte-jsonschema-form) | Svelte 5 library for creating forms based on JSON schema | [Docs](https://x0k.dev/svelte-jsonschema-form/validators/standard-schema/) | -| [RTK Query](https://redux-toolkit.js.org/rtk-query/overview) | A powerful data fetching and caching tool, included with Redux Toolkit | [Docs](https://redux-toolkit.js.org/rtk-query/usage-with-typescript#schema-validation) | -| [Inngest](https://www.inngest.com) | Event-driven durable workflow engine that runs on any cloud | [Docs](https://www.inngest.com/docs/reference/client/create#defining-event-payload-types) | -| [BUPKIS](https://github.com/boneskull/bupkis) | Uncommonly extensible assertions for the beautiful people | [Docs](https://bupkis.zip) | -| [Fragno](https://fragno.dev) | Build framework-agnostic TypeScript libraries that embed backend and frontend logic in user applications | [Docs](https://fragno.dev/docs/for-library-authors/features/route-definition#input-schema) | - -## How can my schema library implement the spec? - -Schema libraries that want to support Standard Schema must implement the `StandardSchemaV1` interface. Start by copying the specification file above into your library. It consists of types only. - -Then implement the spec by adding the `~standard` property to your validator objects/instances. We recommend using `extends` / `implements` to ensure static agreement with the interface. It doesn't matter whether your schema library returns plain objects, functions, or class instances. The only thing that matters is that the `~standard` property is defined somehow. - -Here's a simple worked example of a string validator that implements the spec. - -```ts -import type {StandardSchemaV1} from '@standard-schema/spec'; -// Step 1: Define the schema interface -interface StringSchema extends StandardSchemaV1 { - type: 'string'; - message: string; -} +// ############################### +// ### Standard JSON Schema ### +// ############################### -// Step 2: Implement the schema interface -function string(message: string = 'Invalid type'): StringSchema { - return { - type: 'string', - message, - '~standard': { - version: 1, - vendor: 'valizod', - validate(value) { - return typeof value === 'string' ? {value} : {issues: [{message}]}; - }, - }, - }; +/** The Standard JSON Schema interface. */ +export interface StandardJSONSchemaV1 { + /** The Standard JSON Schema properties. */ + readonly "~standard": StandardJSONSchemaV1.Props; } -``` - -We recommend defining the `~standard.validate()` function in terms of your library's existing validation functions/methods. Ideally implementing the spec only requires a handful of lines of code. - -Avoid returning `Promise` from `~standard.validate()` unless absolutely necessary. Some third-party libraries may not support async validation. - -## How do I accept Standard Schemas in my library? - -Third-party libraries and frameworks can leverage the Standard Schema spec to accept user-defined schemas in a type-safe way. - -To get started, copy and paste the specification file into your project. Alternatively (if you are okay with the extra dependency), you can install the `@standard-schema/spec` package from [npm](https://www.npmjs.com/package/@standard-schema/spec) or [JSR](https://jsr.io/@standard-schema/spec) as a dependency. _It is not recommended to install as a dev dependency, see the [associated FAQ](#can-i-add-it-as-a-dev-dependency) for details_. - -```sh -npm install @standard-schema/spec # npm -yarn add @standard-schema/spec # yarn -pnpm add @standard-schema/spec # pnpm -bun add @standard-schema/spec # bun -deno add jsr:@standard-schema/spec # deno -``` - -Here's a simple example of a generic function that accepts an arbitrary spec-compliant validator and uses it to parse some data. -```ts -import type {StandardSchemaV1} from '@standard-schema/spec'; - -export async function standardValidate( - schema: T, - input: StandardSchemaV1.InferInput -): Promise> { - let result = schema['~standard'].validate(input); - if (result instanceof Promise) result = await result; - - // if the `issues` field exists, the validation failed - if (result.issues) { - throw new Error(JSON.stringify(result.issues, null, 2)); +export declare namespace StandardJSONSchemaV1 { + /** The Standard JSON Schema properties interface. */ + export interface Props + extends StandardTypedV1.Props { + /** Methods for generating the input/output JSON Schema. */ + readonly jsonSchema: StandardJSONSchemaV1.Converter; } - return result.value; -} -``` - -This concise function can accept inputs from any spec-compliant schema library. - -```ts -import * as z from 'zod'; -import * as v from 'valibot'; -import {type} from 'arktype'; - -const zodResult = await standardValidate(z.string(), 'hello'); -const valibotResult = await standardValidate(v.string(), 'hello'); -const arktypeResult = await standardValidate(type('string'), 'hello'); -``` - -## FAQ - -These are the most frequently asked questions about Standard Schema. If your question is not listed, feel free to create an issue. - -### Do I need to add `@standard-schema/spec` as a dependency? - -No. The `@standard-schema/spec` package is completely optional. You can just copy and paste the types into your project. We guarantee no breaking changes without a major version bump. - -If you don't mind additional dependencies, you can add `@standard-schema/spec` as a dependency and consume it with `import type`. The `@standard-schema/spec` package contains no runtime code and only exports types. - -### Can I add it as a dev dependency? - -Despite being types-only, you should _not_ install `@standard-schema/spec` as a dev dependency. By accepting Standard Schemas as part of your public API, the Standard Schema interface becomes a part of your library's public API. As such, it _must_ be available whenever/wherever your library gets installed, even in production installs. For this to happen, it must be installed as a regular dependency. - -### Why did you prefix the `~standard` property with `~`? - -The goal of prefixing the key with `~` is to both avoid conflicts with existing API surfaces and to de-prioritize these keys in auto-complete. The `~` character is one of the few ASCII characters that occurs after `A-Za-z0-9` lexicographically, so VS Code puts these suggestions at the bottom of the list. - -![Screenshot showing the de-prioritization of the `~` prefix keys in VS Code.](https://github.com/standard-schema/standard-schema/assets/3084745/5dfc0219-7531-481e-9691-cff5bc471378) - -### Why not use a symbol key? - -In TypeScript, using a plain `Symbol` inline as a key always collapses to a simple `symbol` type. This would cause conflicts with other schema properties that use symbols. - -```ts -const object = { - [Symbol.for('~output')]: 'some data', -}; -// { [k: symbol]: string } -``` - -Unique symbols can also be declared in a "nominal" way that won't collapse. In this case the symbol key is sorted alphabetically in autocomplete according to the symbol's variable name. - -![Screenshot showing the prioritization of external symbols in VS Code](https://github.com/standard-schema/standard-schema/assets/3084745/82c47820-90c3-4163-a838-858b987a6bea) + /** The Standard JSON Schema converter interface. */ + export interface Converter { + /** Converts the input type to JSON Schema. May throw if conversion is not supported. */ + readonly input: ( + options: StandardJSONSchemaV1.Options + ) => Record; + /** Converts the output type to JSON Schema. May throw if conversion is not supported. */ + readonly output: ( + options: StandardJSONSchemaV1.Options + ) => Record; + } -Thus, these symbol keys don't get sorted to the bottom of the autocomplete list, unlike tilde-prefixed string keys. + /** + * The target version of the generated JSON Schema. + * + * It is *strongly recommended* that implementers support `"draft-2020-12"` and `"draft-07"`, as they are both in wide use. All other targets can be implemented on a best-effort basis. Libraries should throw if they don't support a specified target. + * + * The `"openapi-3.0"` target is intended as a standardized specifier for OpenAPI 3.0 which is a superset of JSON Schema `"draft-04"`. + */ + export type Target = + | "draft-2020-12" + | "draft-07" + | "openapi-3.0" + // Accepts any string for future targets while preserving autocomplete + | ({} & string); + + /** The options for the input/output methods. */ + export interface Options { + /** Specifies the target version of the generated JSON Schema. Support for all versions is on a best-effort basis. If a given version is not supported, the library should throw. */ + readonly target: Target; + + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } -### How to only allow synchronous validation? + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} -The `~standard.validate()` function might return a synchronous value _or_ a `Promise`. If you only accept synchronous validation, you can simply throw an error if the returned value is an instance of `Promise`. Libraries are encouraged to preferentially use synchronous validation whenever possible. + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; -```ts -import type {StandardSchemaV1} from '@standard-schema/spec'; - -function validateInput(schema: StandardSchemaV1, data: unknown) { - const result = schema['~standard'].validate(data); - if (result instanceof Promise) { - throw new TypeError('Schema validation must be synchronous'); - } - // ... + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; } ``` diff --git a/packages/spec/json-schema.md b/packages/spec/json-schema.md new file mode 100644 index 00000000..b55670c3 --- /dev/null +++ b/packages/spec/json-schema.md @@ -0,0 +1,298 @@ +

+ Standard Schema fire logo +
+ Standard JSON Schema

+

+ A standardized JSON Schema representation that preserves inferred type information +
+ standardschema.dev/json-schema +

+
+ + + +Standard JSON Schema is a common interface designed to be implemented by JavaScript and TypeScript entities that _are_ or _can be converted to_ JSON Schema. + +The goal is to make it easier for ecosystem tools to accept user-defined types (typically defined using schema/validation libraries) without needing to write custom logic or adapters for each supported library. And since Standard JSON Schema is a specification, they can do so with no additional runtime dependencies. + +## Motivation + +Many libraries need JSON Schema representations of type information: + +- API documentation generation (e.g. OpenAPI) +- Tool inputs and structured outputs for AI +- Form generation tools +- Code generation + +Previously, type information was commonly destroyed in the process of converting a schema (Zod, ArkType, Valibot) to JSON Schema. This spec provides a standardized, unified JSON Schema representation that preserves inferred type information. + +## Who designed it? + +The spec was designed by the creators of Zod, Valibot, and ArkType. Recent versions of these libraries already implement the spec (see the [full list of compatible libraries](#what-schema-libraries-support-this-spec) below). + +## The interface + +The specification consists of a single TypeScript interface `StandardJSONSchemaV1` to be implemented by any library wishing to be spec-compliant. + +This interface can be found below in its entirety. Libraries wishing to implement the spec can copy/paste the code block below into their codebase. It's also available at `@standard-schema/spec` on [npm](https://www.npmjs.com/package/@standard-schema/spec) and [JSR](https://jsr.io/@standard-schema/spec). + +```typescript +/** The Standard Typed interface. This is a base type extended by other specs. */ +export interface StandardTypedV1 { + /** The Standard properties. */ + readonly '~standard': StandardTypedV1.Props; +} + +export declare namespace StandardTypedV1 { + /** The Standard Typed properties interface. */ + export interface Props { + /** The version number of the standard. */ + readonly version: 1; + /** The vendor name of the schema library. */ + readonly vendor: string; + /** Inferred types associated with the schema. */ + readonly types?: Types | undefined; + } + + /** The Standard Typed types interface. */ + export interface Types { + /** The input type of the schema. */ + readonly input: Input; + /** The output type of the schema. */ + readonly output: Output; + } + + /** Infers the input type of a Standard Typed. */ + export type InferInput = NonNullable< + Schema['~standard']['types'] + >['input']; + + /** Infers the output type of a Standard Typed. */ + export type InferOutput = NonNullable< + Schema['~standard']['types'] + >['output']; +} + +/** The Standard JSON Schema interface. */ +export interface StandardJSONSchemaV1 { + /** The Standard JSON Schema properties. */ + readonly '~standard': StandardJSONSchemaV1.Props; +} + +export declare namespace StandardJSONSchemaV1 { + /** The Standard JSON Schema properties interface. */ + export interface Props + extends StandardTypedV1.Props { + /** Methods for generating the input/output JSON Schema. */ + readonly jsonSchema: StandardJSONSchemaV1.Converter; + } + + /** The Standard JSON Schema converter interface. */ + export interface Converter { + /** Converts the input type to JSON Schema. May throw if conversion is not supported. */ + readonly input: ( + options: StandardJSONSchemaV1.Options + ) => Record; + /** Converts the output type to JSON Schema. May throw if conversion is not supported. */ + readonly output: ( + options: StandardJSONSchemaV1.Options + ) => Record; + } + + /** + * The target version of the generated JSON Schema. + * + * It is *strongly recommended* that implementers support `"draft-2020-12"` and `"draft-07"`, as they are both in wide use. All other targets can be implemented on a best-effort basis. Libraries should throw if they don't support a specified target. + * + * The `"openapi-3.0"` target is intended as a standardized specifier for OpenAPI 3.0 which is a superset of JSON Schema `"draft-04"`. + */ + export type Target = + | 'draft-2020-12' + | 'draft-07' + | 'openapi-3.0' + // Accepts any string: allows future targets while preserving autocomplete + | ({} & string); + + /** The options for the input/output methods. */ + export interface Options { + /** Specifies the target version of the generated JSON Schema. Support for all versions is on a best-effort basis. If a given version is not supported, the library should throw. */ + readonly target: Target; + + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} + + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; + + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; +} +``` + +## Design goals + +The specification meets a few primary design objectives: + +- **Support JSON Schema conversion.** Given a Standard JSON Schema compatible entity, you should be able to generate JSON Schema from it. The spec supports multiple JSON Schema draft versions and formats. +- **Support static type inference.** For TypeScript libraries that do type inference, the specification provides a standard way for them to "advertise" their inferred type, so it can be extracted and used by external tools. +- **Minimal.** It should be easy for libraries to implement this spec in a few lines of code that call their existing functions/methods. +- **Avoid API conflicts.** The entire spec is tucked inside a single object property called `~standard`, which avoids potential naming conflicts with the API surface of existing libraries. +- **Do no harm to DX.** The `~standard` property is tilde-prefixed to [de-prioritize it in autocompletion](https://x.com/colinhacks/status/1816860780459073933). By contrast, an underscore-prefixed property would show up before properties/methods with alphanumeric names. + +## What schema libraries support this spec? + +These are the libraries that have already implemented the Standard JSON Schema interface. (If you maintain a library that implements the spec, [create a PR](https://github.com/standard-schema/standard-schema/compare) to add yourself!) + +The answer to this question is a little more nuanced than with regular _Standard Schema_. The spec can be implemented by any object that _is_ or _can be converted_ to JSON Schema. It's intentionally designed to support multiple use cases. + +- Some schemas may directly encapsulate JSON Schema conversion as a method. These schemas can directly implement the spec. +- For bundle size, reasons, other libraries provide external functions for JSON Schema conversion. In these cases, the _result_ of that function will implement the spec. + +| Implementer | Version(s) | Link | Notes | Usage | +| ------------------------------ | ---------- | ------------------------------------------------------ | ------------------------------------------------------------------------- | -------------- | +| [Zod](https://zod.dev) | v4.2+ | [PR](https://github.com/colinhacks/zod/pull/5477) | | [#](#zod) | +| [ArkType](https://arktype.io/) | v2.1.28 | [PR](https://github.com/arktypeio/arktype/pull/1558) | | [#](#arktype) | +| [Valibot](https://valibot.dev) | v1.2 | [PR](https://github.com/open-circle/valibot/pull/1372) | via `toStandardJsonSchema()` in `@valibot/to-json-schema` package (v1.5+) | [#](#valibot) | +| [Zod Mini](https://zod.dev) | v4.2+ | [PR](https://github.com/colinhacks/zod/pull/5477) | via `z.toJSONSchema()` | [#](#zod-mini) | + +## Usage + +Usage examples for each implementing library. + +### Zod + +```ts +import * as z from 'zod'; + +z.string() satisfies StandardJSONSchemaV1; // βœ… +``` + +### ArkType + +```ts +import {type} from 'arktype'; + +type('string') satisfies StandardJSONSchemaV1; // βœ… +``` + +### Valibot + +```ts +import * as v from 'valibot'; +import {toStandardJsonSchema} from '@valibot/to-json-schema'; + +toStandardJsonSchema(v.string()) satisfies StandardJSONSchemaV1; // βœ… +``` + +### Zod Mini + +```ts +import * as z from 'zod/mini'; + +z.toJSONSchema(z.string()) satisfies StandardJSONSchemaV1; // βœ… +``` + +## What tools / frameworks accept spec-compliant schemas? + +The following tools accept user-defined schemas conforming to the Standard JSON Schema spec. If you maintain a tool that supports Standard JSON Schemas, [create a PR](https://github.com/standard-schema/standard-schema/compare) to add yourself! + +When creating a PR to add your tool, please add a new row to the table below with: + +- **Integrator**: The name of your tool/framework (as a link to your project) +- **Description**: A brief description of your tool. Should be a sentence fragment with no period at the end, e.g. ("Type-safe OpenAPI framework for Express") +- **Link**: A link to PR/commit where support was merged. + +| Integrator | Description | Link | +| ---------- | ----------- | ---- | + +## FAQ + +These are the most frequently asked questions about Standard JSON Schema. If your question is not listed, feel free to create an issue. + +### What's the relationship between this and _Standard Schema_? + +Starting with the next `@standard-schema/spec@1.1.0` version, Standard JSON Schema will be published alongside the existing Standard Schema spec. + +_Standard JSON Schema_ is _orthogonal_ to _Standard Schema_. This interface contains no affordance for data validation. Think of them as "traits" or "interfaces". Any given object/instance/entity can implement one or both. + +### Why multiple `target` values? + +Different tooling requires different versions of JSON Schema. Currently there is a divide in the ecosystem between `"draft-07"` and `"draft-2020-12"`. Library authors that implement this spec are encouraged to implement as many formats as is practical, with a special emphasis on `"draft-07"` and `"draft-2020-12"`. Supporting multiple formats is not required to implement the spec; it is entirely on a best-effort basis. + +### Does the spec account for future versions of JSON Schema? + +Yes, the type signature for `"target"` was intentionally widened with `{} & string`. This allows libraries to support unspecified formats. It also allows the spec to evolve to include future versions of JSON Schema without breaking assignability down the line. + +### What's `"openapi-3.0"`? + +The OpenAPI 3.0 specification (still in wide use) implements its own schema definition format. It's a superset of JSON Schema `"draft-04"` that's augmented with additional keywords like `nullable`. Despite not being an official JSON Schema draft, it's in wide use and has been included in the list of recommended drafts. + +### Why both `jsonSchema.input` and `jsonSchema.output`? + +Many schemas perform transformations during validation. For example, a schema might accept a string as input (`"123"`) but output a number (`123`). The input and output types can differ, so their JSON Schema representations need to differ as well. The `jsonSchema.input` method generates a JSON Schema for the input type, while `jsonSchema.output` generates one for the output type. In cases where input and output types are identical, both methods will return the same schema. + +### What about error handling? + +If a given schema/entity cannot be converted to JSON Schema, the conversion method call may throw. They may also throw if the entity is non-convertible or otherwise cannot be soundly represented as JSON Schema. Any integrating frameworks/libraries should account for this. + +### Why is this a separate spec instead of adding to `StandardSchemaV1`? + +The two concerns are orthogonal. `StandardSchemaV1` is about validation, while `StandardJSONSchemaV1` is about conversion to a JSON Schema representation. Keeping them separate allows greater flexibility for use cases where only one or the other may be required. + +### I'm a schema library author. How do I implement this spec? + +Refer to the [implementation example](https://github.com/standard-schema/standard-schema/blob/main/packages/examples/json-implement.ts) for a worked example. + +### I want to accept JSON Schema from a user. How do I do that? + +Use the interface to accept values from the user. + +```ts +// Function that accepts any compliant `StandardJSONSchemaV1` +// and converts it to a JSON Schema. +export function acceptSchema(schema: StandardJSONSchemaV1) { + // do stuff, e.g. + return schema['~standard'].jsonSchema.input({ + target: 'draft-2020-12', + }); +} + +acceptSchema(z.string()); +``` + +To infer generic type information from the user-defined schema: + +```ts +export function parseData( + schema: T, + data: StandardJSONSchemaV1.InferInput // extract input type +) { + return { + /* ... */ + } as StandardJSONSchemaV1.InferOutput; // extract output type +} +``` + +### What if I want to accept only schemas that implement both `StandardSchema` and `StandardJSONSchema`? + +The two specs are implemented as plain TypeScript interfaces, so you can merge them (and any future specs) as needed for your use case. + +```ts +export interface CombinedProps + extends StandardSchemaV1.Props, + StandardJSONSchemaV1.Props {} + +/** + * An interface that combines StandardJSONSchema and StandardSchema. + * */ +export interface CombinedSpec { + '~standard': CombinedProps; +} +``` diff --git a/packages/spec/jsr.json b/packages/spec/jsr.json index 3fb4bd9b..bde87bba 100644 --- a/packages/spec/jsr.json +++ b/packages/spec/jsr.json @@ -1,5 +1,5 @@ { "name": "@standard-schema/spec", - "version": "1.0.0", + "version": "1.1.0", "exports": "./src/index.ts" } diff --git a/packages/spec/package.json b/packages/spec/package.json index d87adc53..c0fa0414 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,7 +1,7 @@ { "name": "@standard-schema/spec", - "description": "A standard interface for TypeScript schema validation libraries", - "version": "1.0.0", + "description": "A family of specs for interoperable TypeScript", + "version": "1.1.0", "license": "MIT", "author": "Colin McDonnell", "homepage": "https://standardschema.dev", @@ -21,6 +21,7 @@ "types": "./dist/index.d.ts", "exports": { ".": { + "standard-schema-spec": "./src/index.ts", "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" diff --git a/packages/spec/schema.md b/packages/spec/schema.md new file mode 100644 index 00000000..369c5d43 --- /dev/null +++ b/packages/spec/schema.md @@ -0,0 +1,343 @@ +

+ Standard Schema fire logo +
+ Standard Schema

+

+ A common interface for TypeScript validation libraries +
+ standardschema.dev +

+
+ + + +Standard Schema is a common interface designed to be implemented by JavaScript and TypeScript schema libraries. + +The goal is to make it easier for ecosystem tools to accept user-defined type validators, without needing to write custom logic or adapters for each supported library. And since Standard Schema is a specification, they can do so with no additional runtime dependencies. Integrate once, validate anywhere. + +## Who designed it? + +The spec was designed by the creators of Zod, Valibot, and ArkType. Recent versions of these libraries already implement the spec (see the [full list of compatible libraries](#what-schema-libraries-implement-the-spec) below). + +## The interface + +The specification consists of a single TypeScript interface `StandardSchemaV1` to be implemented by any schema library wishing to be spec-compliant. + +This interface can be found below in its entirety. Libraries wishing to implement the spec can copy/paste the code block below into their codebase. It's also available at `@standard-schema/spec` on [npm](https://www.npmjs.com/package/@standard-schema/spec) and [JSR](https://jsr.io/@standard-schema/spec). + +```ts +/** The Standard Schema interface. */ +export interface StandardSchemaV1 { + /** The Standard Schema properties. */ + readonly '~standard': StandardSchemaV1.Props; +} + +export declare namespace StandardSchemaV1 { + /** The Standard Schema properties interface. */ + export interface Props { + /** The version number of the standard. */ + readonly version: 1; + /** The vendor name of the schema library. */ + readonly vendor: string; + /** Validates unknown input values. */ + readonly validate: ( + value: unknown, + options?: StandardSchemaV1.Options | undefined + ) => Result | Promise>; + /** Inferred types associated with the schema. */ + readonly types?: Types | undefined; + } + + /** The result interface of the validate function. */ + export type Result = SuccessResult | FailureResult; + + /** The result interface if validation succeeds. */ + export interface SuccessResult { + /** The typed output value. */ + readonly value: Output; + /** A falsy value for `issues` indicates success. */ + readonly issues?: undefined; + } + + export interface Options { + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + + /** The result interface if validation fails. */ + export interface FailureResult { + /** The issues of failed validation. */ + readonly issues: ReadonlyArray; + } + + /** The issue interface of the failure output. */ + export interface Issue { + /** The error message of the issue. */ + readonly message: string; + /** The path of the issue, if any. */ + readonly path?: ReadonlyArray | undefined; + } + + /** The path segment interface of the issue. */ + export interface PathSegment { + /** The key representing a path segment. */ + readonly key: PropertyKey; + } + + /** The Standard Schema types interface. */ + export interface Types { + /** The input type of the schema. */ + readonly input: Input; + /** The output type of the schema. */ + readonly output: Output; + } + + /** Infers the input type of a Standard Schema. */ + export type InferInput = NonNullable< + Schema['~standard']['types'] + >['input']; + + /** Infers the output type of a Standard Schema. */ + export type InferOutput = NonNullable< + Schema['~standard']['types'] + >['output']; +} +``` + +## Design goals + +The specification meets a few primary design objectives: + +- **Support runtime validation.** Given a Standard Schema compatible validator, you should be able to validate data with it (duh). Any validation errors should be presented in a standardized format. +- **Support static type inference.** For TypeScript libraries that do type inference, the specification provides a standard way for them to "advertise" their inferred type, so it can be extracted and used by external tools. +- **Minimal.** It should be easy for libraries to implement this spec in a few lines of code that call their existing functions/methods. +- **Avoid API conflicts.** The entire spec is tucked inside a single object property called `~standard`, which avoids potential naming conflicts with the API surface of existing libraries. +- **Do no harm to DX.** The `~standard` property is tilde-prefixed to [de-prioritize it in autocompletion](https://x.com/colinhacks/status/1816860780459073933). By contrast, an underscore-prefixed property would show up before properties/methods with alphanumeric names. + +## What schema libraries implement the spec? + +These are the libraries that have already implemented the Standard Schema interface. (If you maintain a library that implements the spec, [create a PR](https://github.com/standard-schema/standard-schema/compare) to add yourself!) + +| Implementer | Version(s) | Link | +| ---------------------------------------------------------------------------------- | ---------- | ---------------------------------------------------------------------------------------------------------- | +| [Zod](https://zod.dev) | 3.24.0+ | [PR](https://github.com/colinhacks/zod/pull/3850) | +| [Valibot](https://valibot.dev/) | v1.0+ | [PR](https://github.com/fabian-hiller/valibot/pull/845) | +| [ArkType](https://arktype.io/) | v2.0+ | [PR](https://github.com/arktypeio/arktype/pull/1194/files) | +| [Effect Schema](https://effect.website/docs/schema/introduction/) (⚠️ via adapter) | v3.13.0+ | [PR](https://github.com/Effect-TS/effect/pull/4359) | +| [Arri Schema](https://github.com/modiimedia/arri) | v0.71.0+ | [PR](https://github.com/modiimedia/arri/pull/130) | +| [TypeBox](https://github.com/sinclairzx81/typebox) (⚠️ via adapter) | v1.0.0+ | [Issue](https://github.com/sinclairzx81/typebox/issues/1365#issuecomment-3369342504) | +| [Formgator](https://github.com/GauBen/formgator) | v0.1.0+ | [Commit](https://github.com/GauBen/formgator/commit/12c8a90) | +| [decoders](https://github.com/nvie/decoders) | v2.6.0+ | [PR](https://github.com/nvie/decoders/pull/1213) | +| [Sury](https://github.com/DZakh/sury) | v9.2.0+ | [PR](https://github.com/DZakh/sury/pull/105) | +| [Skunkteam Types](https://github.com/skunkteam/types) | v9.0.0+ | [PR](https://github.com/skunkteam/types/pull/108) | +| [DreamIt GraphQL-Std-Schema](https://github.com/dreamit-de/graphql-std-schema) | v0.1.0+ | [Commit](https://github.com/dreamit-de/graphql-std-schema/commit/c948d4f56ea72d57744976735bedcaea43d33ae1) | +| [ts.data.json](https://github.com/joanllenas/ts.data.json) | v2.3.0+ | [PR](https://github.com/joanllenas/ts.data.json/pull/35) | +| [quartet](https://github.com/whiteand/ts-quartet) | v11.0.3+ | [PR](https://github.com/whiteand/ts-quartet/pull/9/files) | +| [unhoax](https://sacdenoeuds.github.io/unhoax/) | v0.7.0+ | [Commit](https://github.com/SacDeNoeuds/unhoax/commit/4e7f2d71c98ba9e3e73f029e0af2e46f9980dcbe) | +| [protovalidate-es](https://github.com/bufbuild/protovalidate-es) | v0.5.0+ | [PR](https://github.com/bufbuild/protovalidate-es/pull/54) | +| [remult](https://remult.dev/docs/standard-schema) | v3.1.1+ | [PR](https://github.com/remult/remult/pull/777) | +| [stnl](https://github.com/re-utils/stnl?tab=readme-ov-file#standard-schema) | v1.1.9+ | [Commit](https://github.com/re-utils/stnl/commit/d748522920e6066de6cc5eefa9c93c38f5b0541a) | +| [yup](https://github.com/jquense/yup) | v1.7.0+ | [PR](https://github.com/jquense/yup/pull/2258) | +| [joi](https://github.com/hapijs/joi) | v18.0.0+ | [PR](https://github.com/hapijs/joi/pull/3080) | +| [typia](https://github.com/samchon/typia) | v9.2.0+ | [PR](https://github.com/samchon/typia/pull/1500) | +| [regle](https://github.com/victorgarciaesgi/regle) | v1.9.0+ | [PR](https://github.com/victorgarciaesgi/regle/pull/191) | +| [jsonv-ts](https://github.com/dswbx/jsonv-ts) | v0.3.0+ | [PR](https://github.com/dswbx/jsonv-ts/pull/7) | +| [Evolu](https://github.com/evoluhq/evolu) | v6.0.1+ | [PR](https://github.com/evoluhq/evolu/pull/605) | +| [zeed](https://github.com/holtwick/zeed) | v1.1.0+ | [Commit](https://github.com/holtwick/zeed/commit/05fb980df7ed0722f45a417bad8f1560195c7e1b) | + +## What tools / frameworks accept spec-compliant schemas? + +The following tools accept user-defined schemas conforming to the Standard Schema spec. (If you maintain a tool that supports Standard Schemas, [create a PR](https://github.com/standard-schema/standard-schema/compare) to add yourself!) + +| Integrator | Description | Link | +| ----------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------- | +| [tRPC](https://trpc.io) | Move fast and break nothing, end-to-end typesafe APIs made easy | [PR](https://github.com/trpc/trpc/pull/6079) | +| [TanStack Form](https://tanstack.com/form) | Headless, performant, and type-safe form state management for TS/JS, React, Vue, Angular, Solid, and Lit | [PR](https://github.com/TanStack/form/pull/1020) | +| [TanStack Router](https://tanstack.com/router/latest) | A fully type-safe React router with built-in data fetching, stale-while revalidate caching and first-class search-param APIs | [PR](https://github.com/TanStack/router/pull/2602) | +| [Hono Middleware](https://hono.dev) | Fast, lightweight server, built on Web Standards | [PR](https://github.com/honojs/middleware/pull/887) | +| [Elysia](https://elysiajs.com) | Ergonomic framework for humans | [PR](https://github.com/elysiajs/elysia/pull/1381) | +| [Qwik 🚧](https://qwik.dev/) | Instant-loading web apps, without effort | [PR](https://github.com/QwikDev/qwik/pull/7281) | +| [UploadThing](https://uploadthing.com/) | File uploads for modern web devs | [Docs](https://docs.uploadthing.com/file-routes#input) | +| [T3 Env](https://env.t3.gg/docs/introduction) | Framework agnostic validation for type-safe environment variables | [PR](https://github.com/t3-oss/t3-env/pull/299) | +| [OpenAuth](https://openauth.js.org/) | Universal, standards-based auth provider | [Docs](https://openauth.js.org/docs/#server) | +| [renoun](https://www.renoun.dev/) | The documentation toolkit for React | [Docs](https://www.renoun.dev/utilities/file-system#schema-validation) | +| [Formwerk](https://formwerk.dev/) | A Vue.js framework for building high-quality, accessible, delightful forms | [PR](https://github.com/formwerkjs/formwerk/pull/68) | +| [GQLoom](https://gqloom.dev/) | Weave GraphQL schema and resolvers using Standard Schema | [PR](https://github.com/modevol-com/gqloom/pull/7) | +| [Nuxt UI](https://ui.nuxt.com/) | A UI library for modern web apps, powered by Vue & Tailwind CSS | [PR](https://github.com/nuxt/ui/pull/2303) | +| [oRPC](https://orpc.unnoq.com/) | Typesafe APIs made simple πŸͺ„ | [PR](https://github.com/unnoq/orpc/pull/50) | +| [Regle](https://reglejs.dev/) | Type-safe model-based form validation library for Vue.js | [PR](https://github.com/victorgarciaesgi/regle/pull/46) | +| [upfetch](https://github.com/L-Blondy/up-fetch) | Tiny & composable fetch configuration tool with sensible defaults and built-in schema validation | [PR](https://github.com/L-Blondy/up-fetch/pull/11) | +| [rest-client](https://github.com/logione/rest-client) | Ultra-lightweight and easy-to-use http(s) client for node.js supporting JSON and streams with no external dependencies | [Docs](https://github.com/logione/rest-client) | +| [make-service](https://github.com/gustavoguichard/make-service) | A set of utilities to improve the DX of native `fetch` to better interact with external APIs | [PR](https://github.com/gustavoguichard/make-service/pull/57) | +| [call-api](https://github.com/zayne-labs/callapi) | A lightweight fetching library with retries, interceptors, request deduplication and more | [PR](https://github.com/zayne-labs/call-api/pull/131) | +| [cachified](https://github.com/epicweb-dev/cachified) | Use everything as a cache with type-safety (by Standard Schema), stale-while-revalidate, parallel-fetch deduplication, ... | [Docs](https://github.com/epicweb-dev/cachified?tab=readme-ov-file#type-safety-with-schema-libraries) | +| [React Hook Form](https://github.com/react-hook-form/react-hook-form) | React Hooks for form state management and validation (Web + React Native) | [PR](https://github.com/react-hook-form/resolvers/pull/738) | +| [Mage](https://github.com/deno-mage/server) | Build web applications with Deno and Preact | [Docs](https://deno-mage.com/validation) | +| [Better-fetch](https://github.com/bekacru/better-fetch) | Advanced fetch library for TypeScript with Zod validation, pre-defined routes, hooks, plugins and more | [Docs](https://better-fetch.vercel.app/) | +| [server-act](https://github.com/chungweileong94/server-act) | A simple React server action builder that provides input validation | [PR](https://github.com/chungweileong94/server-act/pull/37) | +| [xink](https://github.com/xinkjs/xink) | A filesystem API router, inspired by SvelteKit | [Docs](https://github.com/xinkjs/xink?tab=readme-ov-file#using-standard-schema) | +| [xsAI](https://github.com/moeru-ai/xsai) | Extra-small AI SDK | [Docs](https://xsai.js.org/docs/packages/tool) | +| [xsSchema](https://github.com/moeru-ai/xsai/blob/main/packages/xsschema) | Extra-small, Standard Schema-based alternative to typeschema | [Docs](https://xsai.js.org/docs/packages-top/xsschema) | +| [xsMCP](https://github.com/moeru-ai/xsmcp) | Extra-small MCP SDK | [Docs](https://xsmcp.js.org/docs/server/shared/tools) | +| [DreamIT GraphQL-Server](https://github.com/dreamit-de/graphql-server) | GraphQL server written in NodeJS/TypeScript | [PR](https://github.com/dreamit-de/graphql-server/pull/479) | +| [Astro Typesafe Routes](https://github.com/feelixe/astro-typesafe-routes) | An Astro integration for typesafe URL generation and routing | [PR](https://github.com/feelixe/astro-typesafe-routes/pull/52) | +| [Muppet](https://github.com/muppet-dev/muppet) | Build MCPs (Model Context Protocol) on top of Hono | [Repo](https://github.com/muppet-dev/muppet) | +| [ts-ics](https://github.com/Neuvernetzung/ts-ics) | Parse and generate iCalendar with TypeScript | [PR](https://github.com/Neuvernetzung/ts-ics/pull/148) | +| [kvdex](https://github.com/oliver-oloughlin/kvdex) | A high-level abstraction layer for Deno KV. Works in Deno, Bun, Node.js and the browser πŸ¦•πŸ—οΈ | [PR](https://github.com/oliver-oloughlin/kvdex/pull/271) | +| [FastMCP](https://github.com/punkpeye/fastmcp) | A TypeScript framework for building MCP servers capable of handling client sessions | [PR](https://github.com/punkpeye/fastmcp/pull/27) | +| [RVF](https://www.rvf-js.io/) | Easy and powerful form state management and validation for React | [PR](https://github.com/airjp73/rvf/pull/431) | +| [SchemQl](https://github.com/a2lix/schemql) | A lightweight TypeScript library that enhances your SQL workflow by combining raw SQL with targeted type safety and schema validation | [Repo](https://github.com/a2lix/schemql) | +| [data-double-dash](https://gitlab.com/alexandre-fernandez/data-double-dash) | Type-safe component framework for multi-page applications | [Docs](https://gitlab.com/alexandre-fernandez/data-double-dash) | +| [Slonik](https://github.com/gajus/slonik) | A Node.js PostgreSQL client with runtime and build time type safety, and composable SQL | [PR](https://github.com/gajus/slonik/pull/695) | +| [Nuxt Safe Runtime Config](https://github.com/onmax/nuxt-safe-runtime-config) | Validate Nuxt runtime config at build time using Zod, Valibot, ArkType, or any Standard Schema compatible library | [Docs](https://github.com/onmax/nuxt-safe-runtime-config) | +| [Content Collections](https://www.content-collections.dev/) | A library for converting content into type-safe data collections | [PR](https://github.com/sdorra/content-collections/pull/570) | +| [safeway](https://github.com/EskiMojo14/safeway) | A type-safe serialisation and validation wrapper for string storage APIs like `localStorage` and `sessionStorage` | [Docs](https://github.com/EskiMojo14/safeway/blob/main/README.md) | +| [True Myth](https://true-myth.js.org) | Safe, idiomatic null, error, and async code handling in TypeScript, with `Maybe`, `Result`, and `Task` types that are really nice | [Guide](https://true-myth.js.org/guide/understanding/standard-schema.html) | +| [@sugardarius/anzen](https://anzen.sugardarius.dev/) | A flexible, framework validation agnostic, type-safe factory for creating Next.JS App Router route handlers | [Docs](https://anzen.sugardarius.dev/) | +| [Conformal](https://github.com/marcomuser/conformal) | A framework-agnostic library for strongly typed FormData parsing | [Repo](https://github.com/marcomuser/conformal) | +| [next-safe-action](https://next-safe-action.dev) | Type safe and validated Server Actions in your Next.js project | [PR](https://github.com/TheEdoRan/next-safe-action/pull/324) | +| [Event Tracker](https://event-tracker.offlegacy.org/en) | A library for event tracking in React applications | [Docs](https://event-tracker.offlegacy.org/en/docs/data-type-validation) | +| [zod-jsonrpc](https://github.com/danscan/zod-jsonrpc) | Type-safe JSON-RPC 2.0 clients and servers | [Docs](https://github.com/danscan/zod-jsonrpc) | +| [xignal](https://github.com/flamrdevs/xignal) | signals state management for real | [Repo](https://github.com/flamrdevs/xignal) | +| [svelte-jsonschema-form](https://github.com/x0k/svelte-jsonschema-form) | Svelte 5 library for creating forms based on JSON schema | [Docs](https://x0k.dev/svelte-jsonschema-form/validators/standard-schema/) | +| [RTK Query](https://redux-toolkit.js.org/rtk-query/overview) | A powerful data fetching and caching tool, included with Redux Toolkit | [Docs](https://redux-toolkit.js.org/rtk-query/usage-with-typescript#schema-validation) | +| [Inngest](https://www.inngest.com) | Event-driven durable workflow engine that runs on any cloud | [Docs](https://www.inngest.com/docs/reference/client/create#defining-event-payload-types) | +| [BUPKIS](https://github.com/boneskull/bupkis) | Uncommonly extensible assertions for the beautiful people | [Docs](https://bupkis.zip) | +| [Fragno](https://fragno.dev) | Build framework-agnostic TypeScript libraries that embed backend and frontend logic in user applications | [Docs](https://fragno.dev/docs/for-library-authors/features/route-definition#input-schema) | + + + + + +## FAQ + +These are the most frequently asked questions about Standard Schema. If your question is not listed, feel free to create an issue. + +### Do I need to add `@standard-schema/spec` as a dependency? + +No. The `@standard-schema/spec` package is completely optional. You can just copy and paste the types into your project. We guarantee no breaking changes without a major version bump. + +If you don't mind additional dependencies, you can add `@standard-schema/spec` as a dependency and consume it with `import type`. The `@standard-schema/spec` package contains no runtime code and only exports types. + +### Can I add it as a dev dependency? + +Despite being types-only, you should _not_ install `@standard-schema/spec` as a dev dependency. By accepting Standard Schemas as part of your public API, the Standard Schema interface becomes a part of your library's public API. As such, it _must_ be available whenever/wherever your library gets installed, even in production installs. For this to happen, it must be installed as a regular dependency. + +### Why did you prefix the `~standard` property with `~`? + +The goal of prefixing the key with `~` is to both avoid conflicts with existing API surfaces and to de-prioritize these keys in auto-complete. The `~` character is one of the few ASCII characters that occurs after `A-Za-z0-9` lexicographically, so VS Code puts these suggestions at the bottom of the list. + +![Screenshot showing the de-prioritization of the `~` prefix keys in VS Code.](https://github.com/standard-schema/standard-schema/assets/3084745/5dfc0219-7531-481e-9691-cff5bc471378) + +### Why not use a symbol key? + +In TypeScript, using a plain `Symbol` inline as a key always collapses to a simple `symbol` type. This would cause conflicts with other schema properties that use symbols. + +```ts +const object = { + [Symbol.for('~output')]: 'some data', +}; +// { [k: symbol]: string } +``` + +Unique symbols can also be declared in a "nominal" way that won't collapse. In this case the symbol key is sorted alphabetically in autocomplete according to the symbol's variable name. + +![Screenshot showing the prioritization of external symbols in VS Code](https://github.com/standard-schema/standard-schema/assets/3084745/82c47820-90c3-4163-a838-858b987a6bea) + +Thus, these symbol keys don't get sorted to the bottom of the autocomplete list, unlike tilde-prefixed string keys. + +### How to only allow synchronous validation? + +The `~standard.validate()` function might return a synchronous value _or_ a `Promise`. If you only accept synchronous validation, you can simply throw an error if the returned value is an instance of `Promise`. Libraries are encouraged to preferentially use synchronous validation whenever possible. + +```ts +import type {StandardSchemaV1} from '@standard-schema/spec'; + +function validateInput(schema: StandardSchemaV1, data: unknown) { + const result = schema['~standard'].validate(data); + if (result instanceof Promise) { + throw new TypeError('Schema validation must be synchronous'); + } + // ... +} +``` diff --git a/packages/spec/src/index.ts b/packages/spec/src/index.ts index 631b059b..90133f64 100644 --- a/packages/spec/src/index.ts +++ b/packages/spec/src/index.ts @@ -1,3 +1,39 @@ +/** The Standard Typed interface. This is a base type extended by other specs. */ +export interface StandardTypedV1 { + /** The Standard properties. */ + readonly "~standard": StandardTypedV1.Props; +} + +export declare namespace StandardTypedV1 { + /** The Standard Typed properties interface. */ + export interface Props { + /** The version number of the standard. */ + readonly version: 1; + /** The vendor name of the schema library. */ + readonly vendor: string; + /** Inferred types associated with the schema. */ + readonly types?: Types | undefined; + } + + /** The Standard Typed types interface. */ + export interface Types { + /** The input type of the schema. */ + readonly input: Input; + /** The output type of the schema. */ + readonly output: Output; + } + + /** Infers the input type of a Standard Typed. */ + export type InferInput = NonNullable< + Schema["~standard"]["types"] + >["input"]; + + /** Infers the output type of a Standard Typed. */ + export type InferOutput = NonNullable< + Schema["~standard"]["types"] + >["output"]; +} + /** The Standard Schema interface. */ export interface StandardSchemaV1 { /** The Standard Schema properties. */ @@ -6,17 +42,13 @@ export interface StandardSchemaV1 { export declare namespace StandardSchemaV1 { /** The Standard Schema properties interface. */ - export interface Props { - /** The version number of the standard. */ - readonly version: 1; - /** The vendor name of the schema library. */ - readonly vendor: string; + export interface Props + extends StandardTypedV1.Props { /** Validates unknown input values. */ readonly validate: ( value: unknown, + options?: StandardSchemaV1.Options | undefined, ) => Result | Promise>; - /** Inferred types associated with the schema. */ - readonly types?: Types | undefined; } /** The result interface of the validate function. */ @@ -26,10 +58,15 @@ export declare namespace StandardSchemaV1 { export interface SuccessResult { /** The typed output value. */ readonly value: Output; - /** The non-existent issues. */ + /** A falsy value for `issues` indicates success. */ readonly issues?: undefined; } + export interface Options { + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + /** The result interface if validation fails. */ export interface FailureResult { /** The issues of failed validation. */ @@ -50,24 +87,77 @@ export declare namespace StandardSchemaV1 { readonly key: PropertyKey; } - /** The Standard Schema types interface. */ - export interface Types { - /** The input type of the schema. */ - readonly input: Input; - /** The output type of the schema. */ - readonly output: Output; + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} + + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; + + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; +} + +/** The Standard JSON Schema interface. */ +export interface StandardJSONSchemaV1 { + /** The Standard JSON Schema properties. */ + readonly "~standard": StandardJSONSchemaV1.Props; +} + +export declare namespace StandardJSONSchemaV1 { + /** The Standard JSON Schema properties interface. */ + export interface Props + extends StandardTypedV1.Props { + /** Methods for generating the input/output JSON Schema. */ + readonly jsonSchema: StandardJSONSchemaV1.Converter; } - /** Infers the input type of a Standard Schema. */ - export type InferInput = NonNullable< - Schema["~standard"]["types"] - >["input"]; + /** The Standard JSON Schema converter interface. */ + export interface Converter { + /** Converts the input type to JSON Schema. May throw if conversion is not supported. */ + readonly input: ( + options: StandardJSONSchemaV1.Options, + ) => Record; + /** Converts the output type to JSON Schema. May throw if conversion is not supported. */ + readonly output: ( + options: StandardJSONSchemaV1.Options, + ) => Record; + } - /** Infers the output type of a Standard Schema. */ - export type InferOutput = NonNullable< - Schema["~standard"]["types"] - >["output"]; + /** + * The target version of the generated JSON Schema. + * + * It is *strongly recommended* that implementers support `"draft-2020-12"` and `"draft-07"`, as they are both in wide use. All other targets can be implemented on a best-effort basis. Libraries should throw if they don't support a specified target. + * + * The `"openapi-3.0"` target is intended as a standardized specifier for OpenAPI 3.0 which is a superset of JSON Schema `"draft-04"`. + */ + export type Target = + | "draft-2020-12" + | "draft-07" + | "openapi-3.0" + // Accepts any string: allows future targets while preserving autocomplete + | ({} & string); + + /** The options for the input/output methods. */ + export interface Options { + /** Specifies the target version of the generated JSON Schema. Support for all versions is on a best-effort basis. If a given version is not supported, the library should throw. */ + readonly target: Target; + + /** Explicit support for additional vendor-specific parameters, if needed. */ + readonly libraryOptions?: Record | undefined; + } + + /** The Standard types interface. */ + export interface Types + extends StandardTypedV1.Types {} + + /** Infers the input type of a Standard. */ + export type InferInput = + StandardTypedV1.InferInput; - // biome-ignore lint/complexity/noUselessEmptyExport: needed for granular visibility control of TS namespace - export {}; + /** Infers the output type of a Standard. */ + export type InferOutput = + StandardTypedV1.InferOutput; } diff --git a/packages/spec/tsconfig.json b/packages/spec/tsconfig.json index b63d8904..c691a141 100644 --- a/packages/spec/tsconfig.json +++ b/packages/spec/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowImportingTsExtensions": true, + "customConditions": ["standard-schema-spec"], "declaration": true, "exactOptionalPropertyTypes": true, "isolatedDeclarations": true, diff --git a/packages/utils/package.json b/packages/utils/package.json index ce2645ae..1a8e4da9 100644 --- a/packages/utils/package.json +++ b/packages/utils/package.json @@ -18,6 +18,7 @@ "types": "./dist/index.d.ts", "exports": { ".": { + "standard-schema-spec": "./src/index.ts", "import": { "types": "./dist/index.d.ts", "default": "./dist/index.js" diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json index b63d8904..c691a141 100644 --- a/packages/utils/tsconfig.json +++ b/packages/utils/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "allowImportingTsExtensions": true, + "customConditions": ["standard-schema-spec"], "declaration": true, "exactOptionalPropertyTypes": true, "isolatedDeclarations": true, diff --git a/packages/web/app/globals.css b/packages/web/app/globals.css index e5d4a3af..3fa477fd 100644 --- a/packages/web/app/globals.css +++ b/packages/web/app/globals.css @@ -110,4 +110,20 @@ article { :is(h2, h3, h4, h5, h6) a.header-anchor { @apply no-underline text-inherit hover:text-gray-300 transition-colors; } + + h2 { + @apply border-b border-gray-500 pb-2; + } + + h3 { + @apply font-bold; + } + + blockquote { + @apply bg-gray-800/50 border-l-4 border-gray-500 rounded-r-lg px-4 py-3 not-italic; + + p { + @apply m-0 text-gray-300; + } + } } \ No newline at end of file diff --git a/packages/web/app/json-schema/page.tsx b/packages/web/app/json-schema/page.tsx new file mode 100644 index 00000000..b3572bbc --- /dev/null +++ b/packages/web/app/json-schema/page.tsx @@ -0,0 +1,135 @@ +import { readFileSync } from "node:fs"; +import { join } from "node:path"; +import { Github } from "lucide-react"; +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { buttonVariants } from "@/components/ui/button"; +import { parseMarkdown } from "@/lib/markdown"; +import { getUrl } from "@/lib/utils"; + +export async function generateMetadata() { + const title = "Standard JSON Schema"; + const description = + "A standardized JSON Schema representation that preserves inferred type information"; + const url = getUrl(); + return { + title, + description, + openGraph: { + title, + description, + siteName: "Standard JSON Schema", + url: `${url}/json-schema`, + locale: "en_US", + images: [ + { + url: `${url}/og.png`, + width: 1200, + height: 630, + alt: "Standard JSON Schema", + }, + ], + }, + twitter: { + card: "summary_large_image", + creator: "@colinhacks", + title, + description, + images: [`${url}/og.png`], + }, + } satisfies Metadata; +} + +export default async function JsonSchemaPage() { + const mdPath = join(process.cwd(), "..", "spec", "json-schema.md"); + const md = readFileSync(mdPath, "utf-8").split("")[1]; + + const html = await parseMarkdown(md); + + return ( +
+
+
+
+ Standard Schema fire logo +
+

+ Introducing +

+
+

+ Standard JSON Schema +

+
+

+ A standardized JSON Schema representation that preserves inferred + type information +

+
+ + + Go to repo + +
+ +
+
+
+
+
+
+
+
+
+ + + Go to repo β†’ + +
+
+

+ by{" "} + + @colinhacks + + ,{" "} + + @fabianhiller + + , and{" "} + + @ssalbdivad + +

+
+

Β© {new Date().getFullYear()}

+
+
+
+ ); +} diff --git a/packages/web/app/page.tsx b/packages/web/app/page.tsx index fd7fe170..a7b12df0 100644 --- a/packages/web/app/page.tsx +++ b/packages/web/app/page.tsx @@ -1,16 +1,17 @@ import { readFileSync } from "node:fs"; import { join } from "node:path"; -import { buttonVariants } from "@/components/ui/button"; -import { parseMarkdown } from "@/lib/markdown"; -import { getUrl } from "@/lib/utils"; -import { Github } from "lucide-react"; +import { ArrowRight, Github } from "lucide-react"; import type { Metadata } from "next"; import Image from "next/image"; import Link from "next/link"; +import { buttonVariants } from "@/components/ui/button"; +import { parseMarkdown } from "@/lib/markdown"; +import { getUrl } from "@/lib/utils"; + +const title = "Standard Schema"; +const description = "A family of specs for interoperable TypeScript"; export async function generateMetadata() { - const title = "Standard Schema"; - const description = "A common interface for TypeScript validation libraries"; const url = getUrl(); return { title, @@ -26,7 +27,7 @@ export async function generateMetadata() { url: `${url}/og.png`, width: 1200, height: 630, - alt: "Introducing Standard Schema", + alt: "Standard Schema logo", }, ], }, @@ -56,24 +57,12 @@ export default async function Home() { width="50" height="50" alt="Standard Schema fire logo" + unoptimized />
-

- Introducing -

-
-

- Standard Schema -

+

{title}

-

- A common interface for TypeScript validation libraries -

+

{description}

-
+
+
+
+ +
+
+

+ Standard Schema +

+

+ For entities that validate and transform data +

+
+ +
+ + +
+
+

+ Standard JSON Schema +

+

+ For JSON Schema and entities that convert to it +

+
+ +
+ +
+
+
")[1]; + + const html = await parseMarkdown(md); + + return ( +
+
+
+
+ Standard Schema fire logo +
+

+ Standard Schema +

+
+

+ A common interface for TypeScript validation libraries +

+
+ + + Go to repo + +
+ +
+
+
+
+
+
+
+
+ +
+ ); +} diff --git a/packages/web/tsconfig.json b/packages/web/tsconfig.json index e7ff3a26..be674a28 100644 --- a/packages/web/tsconfig.json +++ b/packages/web/tsconfig.json @@ -1,12 +1,9 @@ { "compilerOptions": { "target": "ES2017", - "lib": [ - "dom", - "dom.iterable", - "esnext" - ], + "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, + "customConditions": ["standard-schema-spec"], "skipLibCheck": true, "strict": true, "noEmit": true, @@ -23,9 +20,7 @@ } ], "paths": { - "@/*": [ - "./*" - ] + "@/*": ["./*"] } }, "include": [ @@ -35,7 +30,5 @@ ".next/types/**/*.ts", ".next/dev/types/**/*.ts" ], - "exclude": [ - "node_modules" - ] + "exclude": ["node_modules"] }