Skip to content

Commit 40a56f2

Browse files
committed
replace zod with superstruct
1 parent 9f8b4d8 commit 40a56f2

File tree

12 files changed

+127
-80
lines changed

12 files changed

+127
-80
lines changed

packages/next/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,7 @@
9696
"caniuse-lite": "^1.0.30001406",
9797
"postcss": "8.4.14",
9898
"styled-jsx": "5.1.1",
99-
"watchpack": "2.4.0",
100-
"zod": "3.21.4"
99+
"watchpack": "2.4.0"
101100
},
102101
"peerDependencies": {
103102
"@opentelemetry/api": "^1.1.0",
@@ -297,6 +296,7 @@
297296
"string-hash": "1.1.3",
298297
"string_decoder": "1.3.0",
299298
"strip-ansi": "6.0.0",
299+
"superstruct": "1.0.3",
300300
"tar": "6.1.15",
301301
"taskr": "1.1.0",
302302
"terser": "5.14.1",
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
(()=>{var e={318:function(e,t){(function(e,n){true?n(t):0})(this,(function(e){"use strict";class StructError extends TypeError{constructor(e,t){let n;const{message:r,explanation:i,...c}=e;const{path:o}=e;const a=o.length===0?r:`At path: ${o.join(".")} -- ${r}`;super(i??a);if(i!=null)this.cause=a;Object.assign(this,c);this.name=this.constructor.name;this.failures=()=>n??(n=[e,...t()])}}function isIterable(e){return isObject(e)&&typeof e[Symbol.iterator]==="function"}function isObject(e){return typeof e==="object"&&e!=null}function isPlainObject(e){if(Object.prototype.toString.call(e)!=="[object Object]"){return false}const t=Object.getPrototypeOf(e);return t===null||t===Object.prototype}function print(e){if(typeof e==="symbol"){return e.toString()}return typeof e==="string"?JSON.stringify(e):`${e}`}function shiftIterator(e){const{done:t,value:n}=e.next();return t?undefined:n}function toFailure(e,t,n,r){if(e===true){return}else if(e===false){e={}}else if(typeof e==="string"){e={message:e}}const{path:i,branch:c}=t;const{type:o}=n;const{refinement:a,message:s=`Expected a value of type \`${o}\`${a?` with refinement \`${a}\``:""}, but received: \`${print(r)}\``}=e;return{value:r,type:o,refinement:a,key:i[i.length-1],path:i,branch:c,...e,message:s}}function*toFailures(e,t,n,r){if(!isIterable(e)){e=[e]}for(const i of e){const e=toFailure(i,t,n,r);if(e){yield e}}}function*run(e,t,n={}){const{path:r=[],branch:i=[e],coerce:c=false,mask:o=false}=n;const a={path:r,branch:i};if(c){e=t.coercer(e,a);if(o&&t.type!=="type"&&isObject(t.schema)&&isObject(e)&&!Array.isArray(e)){for(const n in e){if(t.schema[n]===undefined){delete e[n]}}}}let s="valid";for(const r of t.validator(e,a)){r.explanation=n.message;s="not_valid";yield[r,undefined]}for(let[u,f,l]of t.entries(e,a)){const t=run(f,l,{path:u===undefined?r:[...r,u],branch:u===undefined?i:[...i,f],coerce:c,mask:o,message:n.message});for(const n of t){if(n[0]){s=n[0].refinement!=null?"not_refined":"not_valid";yield[n[0],undefined]}else if(c){f=n[1];if(u===undefined){e=f}else if(e instanceof Map){e.set(u,f)}else if(e instanceof Set){e.add(f)}else if(isObject(e)){if(f!==undefined||u in e)e[u]=f}}}}if(s!=="not_valid"){for(const r of t.refiner(e,a)){r.explanation=n.message;s="not_refined";yield[r,undefined]}}if(s==="valid"){yield[undefined,e]}}class Struct{constructor(e){const{type:t,schema:n,validator:r,refiner:i,coercer:c=(e=>e),entries:o=function*(){}}=e;this.type=t;this.schema=n;this.entries=o;this.coercer=c;if(r){this.validator=(e,t)=>{const n=r(e,t);return toFailures(n,t,this,e)}}else{this.validator=()=>[]}if(i){this.refiner=(e,t)=>{const n=i(e,t);return toFailures(n,t,this,e)}}else{this.refiner=()=>[]}}assert(e,t){return assert(e,this,t)}create(e,t){return create(e,this,t)}is(e){return is(e,this)}mask(e,t){return mask(e,this,t)}validate(e,t={}){return validate(e,this,t)}}function assert(e,t,n){const r=validate(e,t,{message:n});if(r[0]){throw r[0]}}function create(e,t,n){const r=validate(e,t,{coerce:true,message:n});if(r[0]){throw r[0]}else{return r[1]}}function mask(e,t,n){const r=validate(e,t,{coerce:true,mask:true,message:n});if(r[0]){throw r[0]}else{return r[1]}}function is(e,t){const n=validate(e,t);return!n[0]}function validate(e,t,n={}){const r=run(e,t,n);const i=shiftIterator(r);if(i[0]){const e=new StructError(i[0],(function*(){for(const e of r){if(e[0]){yield e[0]}}}));return[e,undefined]}else{const e=i[1];return[undefined,e]}}function assign(...e){const t=e[0].type==="type";const n=e.map((e=>e.schema));const r=Object.assign({},...n);return t?type(r):object(r)}function define(e,t){return new Struct({type:e,schema:null,validator:t})}function deprecated(e,t){return new Struct({...e,refiner:(t,n)=>t===undefined||e.refiner(t,n),validator(n,r){if(n===undefined){return true}else{t(n,r);return e.validator(n,r)}}})}function dynamic(e){return new Struct({type:"dynamic",schema:null,*entries(t,n){const r=e(t,n);yield*r.entries(t,n)},validator(t,n){const r=e(t,n);return r.validator(t,n)},coercer(t,n){const r=e(t,n);return r.coercer(t,n)},refiner(t,n){const r=e(t,n);return r.refiner(t,n)}})}function lazy(e){let t;return new Struct({type:"lazy",schema:null,*entries(n,r){t??(t=e());yield*t.entries(n,r)},validator(n,r){t??(t=e());return t.validator(n,r)},coercer(n,r){t??(t=e());return t.coercer(n,r)},refiner(n,r){t??(t=e());return t.refiner(n,r)}})}function omit(e,t){const{schema:n}=e;const r={...n};for(const e of t){delete r[e]}switch(e.type){case"type":return type(r);default:return object(r)}}function partial(e){const t=e instanceof Struct?{...e.schema}:{...e};for(const e in t){t[e]=optional(t[e])}return object(t)}function pick(e,t){const{schema:n}=e;const r={};for(const e of t){r[e]=n[e]}return object(r)}function struct(e,t){console.warn("superstruct@0.11 - The `struct` helper has been renamed to `define`.");return define(e,t)}function any(){return define("any",(()=>true))}function array(e){return new Struct({type:"array",schema:e,*entries(t){if(e&&Array.isArray(t)){for(const[n,r]of t.entries()){yield[n,r,e]}}},coercer(e){return Array.isArray(e)?e.slice():e},validator(e){return Array.isArray(e)||`Expected an array value, but received: ${print(e)}`}})}function bigint(){return define("bigint",(e=>typeof e==="bigint"))}function boolean(){return define("boolean",(e=>typeof e==="boolean"))}function date(){return define("date",(e=>e instanceof Date&&!isNaN(e.getTime())||`Expected a valid \`Date\` object, but received: ${print(e)}`))}function enums(e){const t={};const n=e.map((e=>print(e))).join();for(const n of e){t[n]=n}return new Struct({type:"enums",schema:t,validator(t){return e.includes(t)||`Expected one of \`${n}\`, but received: ${print(t)}`}})}function func(){return define("func",(e=>typeof e==="function"||`Expected a function, but received: ${print(e)}`))}function instance(e){return define("instance",(t=>t instanceof e||`Expected a \`${e.name}\` instance, but received: ${print(t)}`))}function integer(){return define("integer",(e=>typeof e==="number"&&!isNaN(e)&&Number.isInteger(e)||`Expected an integer, but received: ${print(e)}`))}function intersection(e){return new Struct({type:"intersection",schema:null,*entries(t,n){for(const r of e){yield*r.entries(t,n)}},*validator(t,n){for(const r of e){yield*r.validator(t,n)}},*refiner(t,n){for(const r of e){yield*r.refiner(t,n)}}})}function literal(e){const t=print(e);const n=typeof e;return new Struct({type:"literal",schema:n==="string"||n==="number"||n==="boolean"?e:null,validator(n){return n===e||`Expected the literal \`${t}\`, but received: ${print(n)}`}})}function map(e,t){return new Struct({type:"map",schema:null,*entries(n){if(e&&t&&n instanceof Map){for(const[r,i]of n.entries()){yield[r,r,e];yield[r,i,t]}}},coercer(e){return e instanceof Map?new Map(e):e},validator(e){return e instanceof Map||`Expected a \`Map\` object, but received: ${print(e)}`}})}function never(){return define("never",(()=>false))}function nullable(e){return new Struct({...e,validator:(t,n)=>t===null||e.validator(t,n),refiner:(t,n)=>t===null||e.refiner(t,n)})}function number(){return define("number",(e=>typeof e==="number"&&!isNaN(e)||`Expected a number, but received: ${print(e)}`))}function object(e){const t=e?Object.keys(e):[];const n=never();return new Struct({type:"object",schema:e?e:null,*entries(r){if(e&&isObject(r)){const i=new Set(Object.keys(r));for(const n of t){i.delete(n);yield[n,r[n],e[n]]}for(const e of i){yield[e,r[e],n]}}},validator(e){return isObject(e)||`Expected an object, but received: ${print(e)}`},coercer(e){return isObject(e)?{...e}:e}})}function optional(e){return new Struct({...e,validator:(t,n)=>t===undefined||e.validator(t,n),refiner:(t,n)=>t===undefined||e.refiner(t,n)})}function record(e,t){return new Struct({type:"record",schema:null,*entries(n){if(isObject(n)){for(const r in n){const i=n[r];yield[r,r,e];yield[r,i,t]}}},validator(e){return isObject(e)||`Expected an object, but received: ${print(e)}`}})}function regexp(){return define("regexp",(e=>e instanceof RegExp))}function set(e){return new Struct({type:"set",schema:null,*entries(t){if(e&&t instanceof Set){for(const n of t){yield[n,n,e]}}},coercer(e){return e instanceof Set?new Set(e):e},validator(e){return e instanceof Set||`Expected a \`Set\` object, but received: ${print(e)}`}})}function string(){return define("string",(e=>typeof e==="string"||`Expected a string, but received: ${print(e)}`))}function tuple(e){const t=never();return new Struct({type:"tuple",schema:null,*entries(n){if(Array.isArray(n)){const r=Math.max(e.length,n.length);for(let i=0;i<r;i++){yield[i,n[i],e[i]||t]}}},validator(e){return Array.isArray(e)||`Expected an array, but received: ${print(e)}`}})}function type(e){const t=Object.keys(e);return new Struct({type:"type",schema:e,*entries(n){if(isObject(n)){for(const r of t){yield[r,n[r],e[r]]}}},validator(e){return isObject(e)||`Expected an object, but received: ${print(e)}`},coercer(e){return isObject(e)?{...e}:e}})}function union(e){const t=e.map((e=>e.type)).join(" | ");return new Struct({type:"union",schema:null,coercer(t){for(const n of e){const[e,r]=n.validate(t,{coerce:true});if(!e){return r}}return t},validator(n,r){const i=[];for(const t of e){const[...e]=run(n,t,r);const[c]=e;if(!c[0]){return[]}else{for(const[t]of e){if(t){i.push(t)}}}}return[`Expected the value to satisfy a union of \`${t}\`, but received: ${print(n)}`,...i]}})}function unknown(){return define("unknown",(()=>true))}function coerce(e,t,n){return new Struct({...e,coercer:(r,i)=>is(r,t)?e.coercer(n(r,i),i):e.coercer(r,i)})}function defaulted(e,t,n={}){return coerce(e,unknown(),(e=>{const r=typeof t==="function"?t():t;if(e===undefined){return r}if(!n.strict&&isPlainObject(e)&&isPlainObject(r)){const t={...e};let n=false;for(const e in r){if(t[e]===undefined){t[e]=r[e];n=true}}if(n){return t}}return e}))}function trimmed(e){return coerce(e,string(),(e=>e.trim()))}function empty(e){return refine(e,"empty",(t=>{const n=getSize(t);return n===0||`Expected an empty ${e.type} but received one with a size of \`${n}\``}))}function getSize(e){if(e instanceof Map||e instanceof Set){return e.size}else{return e.length}}function max(e,t,n={}){const{exclusive:r}=n;return refine(e,"max",(n=>r?n<t:n<=t||`Expected a ${e.type} less than ${r?"":"or equal to "}${t} but received \`${n}\``))}function min(e,t,n={}){const{exclusive:r}=n;return refine(e,"min",(n=>r?n>t:n>=t||`Expected a ${e.type} greater than ${r?"":"or equal to "}${t} but received \`${n}\``))}function nonempty(e){return refine(e,"nonempty",(t=>{const n=getSize(t);return n>0||`Expected a nonempty ${e.type} but received an empty one`}))}function pattern(e,t){return refine(e,"pattern",(n=>t.test(n)||`Expected a ${e.type} matching \`/${t.source}/\` but received "${n}"`))}function size(e,t,n=t){const r=`Expected a ${e.type}`;const i=t===n?`of \`${t}\``:`between \`${t}\` and \`${n}\``;return refine(e,"size",(e=>{if(typeof e==="number"||e instanceof Date){return t<=e&&e<=n||`${r} ${i} but received \`${e}\``}else if(e instanceof Map||e instanceof Set){const{size:c}=e;return t<=c&&c<=n||`${r} with a size ${i} but received one with a size of \`${c}\``}else{const{length:c}=e;return t<=c&&c<=n||`${r} with a length ${i} but received one with a length of \`${c}\``}}))}function refine(e,t,n){return new Struct({...e,*refiner(r,i){yield*e.refiner(r,i);const c=n(r,i);const o=toFailures(c,i,e,r);for(const e of o){yield{...e,refinement:t}}}})}e.Struct=Struct;e.StructError=StructError;e.any=any;e.array=array;e.assert=assert;e.assign=assign;e.bigint=bigint;e.boolean=boolean;e.coerce=coerce;e.create=create;e.date=date;e.defaulted=defaulted;e.define=define;e.deprecated=deprecated;e.dynamic=dynamic;e.empty=empty;e.enums=enums;e.func=func;e.instance=instance;e.integer=integer;e.intersection=intersection;e.is=is;e.lazy=lazy;e.literal=literal;e.map=map;e.mask=mask;e.max=max;e.min=min;e.never=never;e.nonempty=nonempty;e.nullable=nullable;e.number=number;e.object=object;e.omit=omit;e.optional=optional;e.partial=partial;e.pattern=pattern;e.pick=pick;e.record=record;e.refine=refine;e.regexp=regexp;e.set=set;e.size=size;e.string=string;e.struct=struct;e.trimmed=trimmed;e.tuple=tuple;e.type=type;e.union=union;e.unknown=unknown;e.validate=validate}))}};if(typeof __nccwpck_require__!=="undefined")__nccwpck_require__.ab=__dirname+"/";var t={};e[318](0,t);module.exports=t})();
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"name":"superstruct","main":"index.cjs","license":"MIT"}

packages/next/src/compiled/zod/LICENSE

Lines changed: 0 additions & 21 deletions
This file was deleted.

packages/next/src/compiled/zod/index.js

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/next/src/compiled/zod/package.json

Lines changed: 0 additions & 1 deletion
This file was deleted.

packages/next/src/server/app-render/parse-and-validate-flight-router-state.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { FlightRouterState } from './types'
22
import { flightRouterStateSchema } from './types'
3+
import { assert } from 'next/dist/compiled/superstruct'
34

45
export function parseAndValidateFlightRouterState(
56
stateHeader: string | string[] | undefined
@@ -23,9 +24,9 @@ export function parseAndValidateFlightRouterState(
2324
}
2425

2526
try {
26-
return flightRouterStateSchema.parse(
27-
JSON.parse(decodeURIComponent(stateHeader))
28-
)
27+
const state = JSON.parse(decodeURIComponent(stateHeader))
28+
assert(state, flightRouterStateSchema)
29+
return state
2930
} catch {
3031
throw new Error('The router state header was sent but could not be parsed.')
3132
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { flightRouterStateSchema } from './types'
2+
import { assert } from 'next/dist/compiled/superstruct'
3+
4+
const validFixtures = [
5+
[
6+
['a', 'b', 'c'],
7+
{
8+
a: [['a', 'b', 'c'], {}],
9+
b: [['a', 'b', 'c'], {}],
10+
},
11+
],
12+
[
13+
['a', 'b', 'c'],
14+
{
15+
a: [['a', 'b', 'c'], {}],
16+
b: [['a', 'b', 'c'], {}],
17+
},
18+
null,
19+
null,
20+
true,
21+
],
22+
[
23+
['a', 'b', 'c'],
24+
{
25+
a: [['a', 'b', 'c'], {}],
26+
b: [['a', 'b', 'c'], {}],
27+
},
28+
null,
29+
'refetch',
30+
],
31+
]
32+
33+
const invalidFixtures = [
34+
// plain wrong
35+
['1', 'b', 'c'],
36+
// invalid enum
37+
[['a', 'b', 'foo'], {}],
38+
// invalid url
39+
[
40+
['a', 'b', 'c'],
41+
{
42+
a: [['a', 'b', 'c'], {}],
43+
b: [['a', 'b', 'c'], {}],
44+
},
45+
{
46+
invalid: 'invalid',
47+
},
48+
],
49+
// invalid isRootLayout
50+
[
51+
['a', 'b', 'c'],
52+
{
53+
a: [['a', 'b', 'c'], {}],
54+
b: [['a', 'b', 'c'], {}],
55+
},
56+
null,
57+
1,
58+
],
59+
]
60+
61+
describe('flightRouterStateSchema', () => {
62+
it('should validate a correct flight router state', () => {
63+
for (const state of validFixtures) {
64+
expect(() => assert(state, flightRouterStateSchema)).not.toThrow()
65+
}
66+
})
67+
it('should not validate an incorrect flight router state', () => {
68+
for (const state of invalidFixtures) {
69+
expect(() => assert(state, flightRouterStateSchema)).toThrow()
70+
}
71+
})
72+
})

packages/next/src/server/app-render/types.ts

Lines changed: 24 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,55 +5,35 @@ import type { ClientReferenceManifest } from '../../build/webpack/plugins/flight
55
import type { NextFontManifest } from '../../build/webpack/plugins/next-font-manifest-plugin'
66
import type { ParsedUrlQuery } from 'querystring'
77

8-
import zod from 'zod'
8+
import * as s from 'next/dist/compiled/superstruct'
99

1010
export type DynamicParamTypes = 'catchall' | 'optional-catchall' | 'dynamic'
1111

12-
const dynamicParamTypesSchema = zod.enum(['c', 'oc', 'd'])
13-
/**
14-
* c = catchall
15-
* oc = optional catchall
16-
* d = dynamic
17-
*/
18-
export type DynamicParamTypesShort = zod.infer<typeof dynamicParamTypesSchema>
12+
const dynamicParamTypesSchema = s.enums(['c', 'oc', 'd'])
13+
14+
export type DynamicParamTypesShort = s.Infer<typeof dynamicParamTypesSchema>
1915

20-
const segmentSchema = zod.union([
21-
zod.string(),
22-
zod.tuple([zod.string(), zod.string(), dynamicParamTypesSchema]),
16+
const segmentSchema = s.union([
17+
s.string(),
18+
s.tuple([s.string(), s.string(), dynamicParamTypesSchema]),
2319
])
24-
/**
25-
* Segment in the router state.
26-
*/
27-
export type Segment = zod.infer<typeof segmentSchema>
28-
29-
export const flightRouterStateSchema: zod.ZodType<FlightRouterState> = zod.lazy(
30-
() => {
31-
const parallelRoutesSchema = zod.record(flightRouterStateSchema)
32-
const urlSchema = zod.string().nullable().optional()
33-
const refreshSchema = zod.literal('refetch').nullable().optional()
34-
const isRootLayoutSchema = zod.boolean().optional()
35-
36-
// Due to the lack of optional tuple types in Zod, we need to use union here.
37-
// https://github.com/colinhacks/zod/issues/1465
38-
return zod.union([
39-
zod.tuple([
40-
segmentSchema,
41-
parallelRoutesSchema,
42-
urlSchema,
43-
refreshSchema,
44-
isRootLayoutSchema,
45-
]),
46-
zod.tuple([
47-
segmentSchema,
48-
parallelRoutesSchema,
49-
urlSchema,
50-
refreshSchema,
51-
]),
52-
zod.tuple([segmentSchema, parallelRoutesSchema, urlSchema]),
53-
zod.tuple([segmentSchema, parallelRoutesSchema]),
54-
])
55-
}
56-
)
20+
21+
export type Segment = s.Infer<typeof segmentSchema>
22+
23+
// unfortunately the tuple is not understood well by Describe so we have to
24+
// use any here. This does not have any impact on the runtime type since the validation
25+
// does work correctly.
26+
export const flightRouterStateSchema: s.Describe<any> = s.tuple([
27+
segmentSchema,
28+
s.record(
29+
s.string(),
30+
s.lazy(() => flightRouterStateSchema)
31+
),
32+
s.optional(s.nullable(s.string())),
33+
s.optional(s.nullable(s.literal('refetch'))),
34+
s.optional(s.boolean()),
35+
])
36+
5737
/**
5838
* Router state
5939
*/

packages/next/taskfile.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1991,6 +1991,16 @@ export async function ncc_unistore(task, opts) {
19911991
.ncc({ packageName: 'unistore', externals })
19921992
.target('src/compiled/unistore')
19931993
}
1994+
1995+
// eslint-disable-next-line camelcase
1996+
externals['unistore'] = 'next/dist/compiled/superstruct'
1997+
export async function ncc_superstruct(task, opts) {
1998+
await task
1999+
.source(relative(__dirname, require.resolve('superstruct')))
2000+
.ncc({ packageName: 'superstruct', externals })
2001+
.target('src/compiled/superstruct')
2002+
}
2003+
19942004
// eslint-disable-next-line camelcase
19952005
externals['web-vitals'] = 'next/dist/compiled/web-vitals'
19962006
export async function ncc_web_vitals(task, opts) {
@@ -2314,6 +2324,7 @@ export async function ncc(task, opts) {
23142324
'ncc_source_map',
23152325
'ncc_string_hash',
23162326
'ncc_strip_ansi',
2327+
'ncc_superstruct',
23172328
'ncc_nft',
23182329
'ncc_tar',
23192330
'ncc_terser',

0 commit comments

Comments
 (0)