Standard Schema
How PermDock consumes Standard Schema v1 and Standard JSON Schema so resources can be defined with Zod, Valibot, ArkType or Effect Schema without adapters.
What it is
Standard Schema is a small interface that validation libraries implement so downstream tools can accept any of them without per-library adapters. The spec is published as the types-only package @standard-schema/spec and defines three interfaces under a single ~standard key:
StandardTypedV1: the base.version: 1,vendor, and optionaltypes: { input, output }used purely for type inference.StandardSchemaV1: addsvalidate(value, options?), which returns either{ value }or{ issues: [{ message, path? }] }, synchronously or as a Promise.StandardJSONSchemaV1(Standard JSON Schema): addsjsonSchema.input(opts)/jsonSchema.output(opts)with atargetsuch asdraft-2020-12,draft-07oropenapi-3.0.
Type extraction is StandardSchemaV1.InferInput and StandardSchemaV1.InferOutput, which read the types slot. The spec documents the pattern that sync-only consumers may throw when validate returns a Promise.
Implementers listed on the site include Zod 3.24+, Valibot 1.0+, ArkType 2.0+ and Effect Schema 3.13+ (via adapter), plus yup, joi, typia, Mongoose, VineJS and many smaller libraries. Consumers include tRPC, TanStack Form and Router, Hono middleware, Elysia, oRPC, React Hook Form and several MCP server frameworks.
Why it matters for PermDock
A permissions library has to know the shape of the things it protects. CASL infers nothing from schemas; permix asked users to write parallel interfaces; Kilpi hand-types resource objects per policy. Only @zap-studio/permit builds on Standard Schema, and it has no adapters. PermDock takes the same foundation and uses it in four places:
- Types.
resource(Post, ...)infers the instance type fromInferOutputsopermissions.post.updateis aPermissionoverPostandcan(permissions.post.update, post)type-checks the second argument. - Conditions. Portable
where/checkconditions are typed against the schema output, so{ where: { authorId: principal.id } }fails to compile ifPosthas noauthorId. - Boundary validation. Data that crossed a trust boundary (HTTP body, MCP tool arguments, client
refresh) is validated with the resource schema before a rule runs. See validation and validation. - Export. Standard JSON Schema turns the same resource schemas into JSON Schema for the catalog,
permdock catalog, and OpenAPIcomponents, without Zod-specific converters.
Because the interface is a dependency-free set of types, PermDock keeps its zero-runtime-dependency budget while supporting every validator on the list.
How PermDock uses it
import { definePermissions, resource } from "permdock";
import { z } from "zod"; // or valibot / arktype / effect: any Standard Schema
const Post = z.object({
id: z.string(),
authorId: z.string(),
orgId: z.string(),
published: z.boolean(),
});
export const permissions = definePermissions({
post: resource(Post, {
id: "id",
actions: ["read", "update", "delete", "publish"],
collection: ["create", "list"],
}),
billing: {
plan: resource({ collection: ["view", "change"] }), // schema-less resource: no instance type, no validation
},
});Rules of consumption:
resource()accepts anything that satisfiesStandardSchemaV1. It reads~standard.types.outputfor inference and stores the schema on the resource node, never on the permission leaf, so leaves stay plain JSON.- Validation is synchronous at boundaries.
validate: 'boundary'(the default indefinePolicy) calls~standard.validateon untrusted data. If the validator returns a Promise,decideandassertthrowPermDockValidationErrorwith codeasync-schemarather than silently denying (canstill returnsfalse, never throws), which is the failure mode@zap-studio/permithas with async rules.validate: 'always'and'never'are the other two modes. - Issues become denials. A failed validation yields
outcome: 'denied'with a reason of kindvalidationcarrying theissuesarray, so an MCP client or an HTTP caller sees which field was wrong. - JSON Schema export calls
~standard.jsonSchema.output({ target: 'draft-2020-12' })when the validator implementsStandardJSONSchemaV1; a schema-less resource, a validator without it and a converter that throws all produceschema: nullin the catalog. @standard-schema/specis a regular dependency ofpermdock, not a peer, because its types are part of PermDock's public API. Users never install it themselves.- Generated definitions from
permdock rls importemit code for the validator chosen with--schema zod|valibot|arktype, or reference existing Drizzle tables viadrizzle-zod; the output is still just a Standard Schema.
Mapping table
| Standard Schema concept | PermDock concept |
|---|---|
StandardSchemaV1 object | First argument of resource(schema, options) |
InferOutput | Instance type of Permission leaves, filter element type, condition field names |
InferInput | Not used; PermDock reasons about parsed values |
validate returning { value } | Rule receives the validated value |
validate returning { issues } | Decision denied with reason.kind === 'validation' and the issues |
validate returning a Promise | PermDockValidationError at the boundary (sync requirement) |
StandardJSONSchemaV1.jsonSchema.output | Catalog JSON Schema, OpenAPI components.schemas, MCP inputSchema cross-check |
vendor | Not read; every implementer is treated the same |
Supported validators
Any implementer works. The ones PermDock documents and tests in tests/types are Zod, Valibot, ArkType and Effect Schema. Schema-less resources (resource({ collection: [...] })) are supported for actions that have no instance.
The boundary is strictly synchronous: there is no async validation mode, and an Effect Schema user passes its synchronous adapter. Field-level grants type their fields list from InferOutput, so a field name the schema does not declare is a type error. permdock rls import ships no built-in schema object: generated definitions import the validator named by --schema (Zod by default).
Sources
- standardschema.dev: specification, interfaces, implementer and consumer tables.
- Standard JSON Schema.
- standard-schema/standard-schema on GitHub.
- Landscape research for how existing libraries, including zap-studio/permit, consume the spec.
Last updated on
Standards
Why PermDock is standards-first and which specification each part of the library implements or targets.
OpenAPI 3.2 and 3.3
How PermDock emits OpenAPI 3.2 security schemes, per-operation security and x-permdock-permissions (with registered fallbacks for 3.1), imports documents into a catalog, and builds the pinned OpenAPI 3.3 Security Profile draft next to the stable x-permdock-securityProfile twin.