diff --git a/.gitignore b/.gitignore index 610f8a0..66ae94f 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ dist node_modules coverage +.next # Logs logs diff --git a/docs/content/docs/api.mdx b/docs/content/docs/api.mdx new file mode 100644 index 0000000..b016324 --- /dev/null +++ b/docs/content/docs/api.mdx @@ -0,0 +1,322 @@ +--- +title: Defining schemas +description: Learn how to create and use schemas with dynz +--- + +import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; +import { Callout } from 'fumadocs-ui/components/callout'; + +# Defining Schemas + +To validate data, you must first define a schema. Schemas represent types, from simple primitive values to complex nested objects and arrays. + +# Primitives +```typescript + +import { string, number, object, validate, equals, lt, min, max } from 'dynz' + +// Primitive types +string() +number() +boolean() +dateString() + +``` + +{/* STRING: "string", + DATE: "date", + DATE_STRING: "date_string", + NUMBER: "number", + OBJECT: "object", + ARRAY: "array", + OPTIONS: "options", + BOOLEAN: "boolean", + FILE: "file", */} + + +```typescript +import { string, number, object, validate, equals, lt, min, max } from 'dynz' + +// Define a schema +const schema = object({ + fields: { + name: string({ + rules: [min(2)] + }), + age: number({ + rules: [min(0), max(120)] + }), + parentalApproval: boolean({ + rules: [equals(true)], + // `parentalApproval` field is only included when age is under 18 + included: lt('age', 18) + }) + } +}) + +// Validate data +const result = validate(userSchema, { + name: "John Doe", + age: 16, + parentalApproval: false +}) + +if (result.success) { + console.log("Valid data:", result.data) +} else { + console.log("Validation errors:", result.errors) +} +``` + +## Installation + + + + ```bash + pnpm add dynz + ``` + + + ```bash + npm install dynz + ``` + + + ```bash + yarn add dynz + ``` + + + +## Basic Usage + +### Creating Your First Schema + +Start by importing the schema builders and validation function: + +```typescript +import { string, number, object, validate } from 'dynz' + +// Define a schema +const userSchema = object({ + fields: { + name: string(), + age: number({ + rules: [min(0), max(120)] + }), + parentalApproval: boolean({ + rules: [equals(true)], + // field is only included when age is under 18 + included: lt('age', 18) + }) + } +}) + +// Some data +const data = { .... } + +// Validate data +const result = validate(userSchema, undefined, data) + +if (result.success) { + console.log("Valid data:", result.data) +} else { + console.log("Validation errors:", result.errors) +} +``` + +### Schema Types + +dynz supports several built-in schema types: + +#### String Schema + +```typescript +import { string } from 'dynz' + +const nameSchema = string() + .min(2, "Name must be at least 2 characters") + .max(50, "Name must not exceed 50 characters") + .regex(/^[a-zA-Z\s]+$/, "Name can only contain letters and spaces") + +const emailSchema = string() + .email("Please enter a valid email address") +``` + +#### Number Schema + +```typescript +import { number } from 'dynz' + +const ageSchema = number() + .min(0, "Age must be positive") + .max(120, "Age must be realistic") + +const priceSchema = number() + .positive("Price must be positive") +``` + +#### Object Schema + +```typescript +import { object, string, number } from 'dynz' + +const productSchema = object({ + name: string().min(1), + price: number().positive(), + description: string().optional() +}) +``` + +#### Array Schema + +```typescript +import { array, string } from 'dynz' + +const tagsSchema = array(string().min(1)) + .min(1, "At least one tag is required") + .max(5, "Maximum 5 tags allowed") +``` + +### Conditional Validation + +dynz excels at dynamic, conditional validation based on other field values: + +```typescript +import { object, string, conditions } from 'dynz' + +const userSchema = object({ + accountType: string().oneOf(['personal', 'business']), + + // Only required if accountType is 'business' + companyName: string() + .requiredIf(conditions.equals('accountType', 'business')) + .min(2), + + // Different validation rules based on account type + email: string() + .email() + .ruleIf( + conditions.equals('accountType', 'business'), + string().regex(/@company\.com$/, "Business accounts must use company email") + ) +}) +``` + +### Framework Integration + +#### Vue.js with VeeValidate + + + Install the VeeValidate integration: `pnpm add @dynz/veevalidate` + + +```typescript +import { useForm } from 'vee-validate' +import { toDynzValidator } from '@dynz/veevalidate' +import { object, string } from 'dynz' + +const schema = object({ + username: string().min(3), + password: string().min(8) +}) + +const { defineField, handleSubmit, errors } = useForm({ + validationSchema: toDynzValidator(schema) +}) + +const [username, usernameAttrs] = defineField('username') +const [password, passwordAttrs] = defineField('password') +``` + +#### React (with custom hook) + +```typescript +import { useState } from 'react' +import { validate } from 'dynz' + +function useFormValidation(schema) { + const [errors, setErrors] = useState({}) + + const validateField = (name, value, allValues) => { + const result = validate(schema, { ...allValues, [name]: value }) + if (!result.success) { + setErrors(prev => ({ + ...prev, + [name]: result.errors[name]?.[0] + })) + } else { + setErrors(prev => { + const newErrors = { ...prev } + delete newErrors[name] + return newErrors + }) + } + } + + return { errors, validateField } +} +``` + +## Advanced Features + +### Private Field Masking + +Mark sensitive fields as private to automatically mask them in validation results: + +```typescript +import { object, string } from 'dynz' + +const userSchema = object({ + username: string(), + password: string().private(), // Will be masked in results + confirmPassword: string().private() +}) +``` + +### Cross-Field Validation + +Reference other fields in your validation rules: + +```typescript +import { object, string, conditions } from 'dynz' + +const signupSchema = object({ + password: string().min(8), + confirmPassword: string() + .equals(conditions.ref('password'), "Passwords must match") +}) +``` + +### Custom Rules + +Create your own validation rules: + +```typescript +import { string, customRule } from 'dynz' + +const strongPasswordRule = customRule((value) => { + const hasUppercase = /[A-Z]/.test(value) + const hasLowercase = /[a-z]/.test(value) + const hasNumbers = /\d/.test(value) + const hasSpecialChar = /[!@#$%^&*]/.test(value) + + return hasUppercase && hasLowercase && hasNumbers && hasSpecialChar +}, "Password must contain uppercase, lowercase, numbers, and special characters") + +const passwordSchema = string() + .min(8) + .custom(strongPasswordRule) +``` + +## Next Steps + +Now that you understand the basics, explore more advanced topics: + +- **[API Reference](/docs/api)** - Complete API documentation +- **[Examples](/docs/examples)** - Real-world usage examples +- **[Framework Integrations](/docs/integrations)** - Vue, React, and other framework guides + + + Check out the [examples directory](https://github.com/your-username/dynz/tree/main/examples) in the repository for complete working examples with Vue.js and Next.js. + \ No newline at end of file diff --git a/docs/content/docs/dynamic.mdx b/docs/content/docs/dynamic.mdx new file mode 100644 index 0000000..1ca0782 --- /dev/null +++ b/docs/content/docs/dynamic.mdx @@ -0,0 +1,328 @@ +--- +title: Dynamic Schemas +description: Learn how to create and use schemas with dynz +--- + +import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; +import { Callout } from 'fumadocs-ui/components/callout'; + +# Dynamic Schemas + +Dynamic schema explanation here + +# Primitives +```typescript + +import { string, number, object, validate, equals, lt, min, max } from 'dynz' + +// Dynamically include fields based on other field values +const schema = object({ + fields: { + age: number({ + rules: [min(0), max(120)] + }), + parentalApproval: boolean({ + rules: [equals(true)], + // `parentalApproval` field is only included when age is under 18 + included: lt('age', 18) + }) +} +``` + +{/* STRING: "string", + DATE: "date", + DATE_STRING: "date_string", + NUMBER: "number", + OBJECT: "object", + ARRAY: "array", + OPTIONS: "options", + BOOLEAN: "boolean", + FILE: "file", */} + + +```typescript +import { string, number, object, validate, equals, lt, min, max } from 'dynz' + +// Define a schema +const schema = object({ + fields: { + name: string({ + rules: [min(2)] + }), + age: number({ + rules: [min(0), max(120)] + }), + parentalApproval: boolean({ + rules: [equals(true)], + // `parentalApproval` field is only included when age is under 18 + included: lt('age', 18) + }) + } +}) + +// Validate data +const result = validate(userSchema, { + name: "John Doe", + age: 16, + parentalApproval: false +}) + +if (result.success) { + console.log("Valid data:", result.data) +} else { + console.log("Validation errors:", result.errors) +} +``` + +## Installation + + + + ```bash + pnpm add dynz + ``` + + + ```bash + npm install dynz + ``` + + + ```bash + yarn add dynz + ``` + + + +## Basic Usage + +### Creating Your First Schema + +Start by importing the schema builders and validation function: + +```typescript +import { string, number, object, validate } from 'dynz' + +// Define a schema +const userSchema = object({ + fields: { + name: string(), + age: number({ + rules: [min(0), max(120)] + }), + parentalApproval: boolean({ + rules: [equals(true)], + // field is only included when age is under 18 + included: lt('age', 18) + }) + } +}) + +// Some data +const data = { .... } + +// Validate data +const result = validate(userSchema, undefined, data) + +if (result.success) { + console.log("Valid data:", result.data) +} else { + console.log("Validation errors:", result.errors) +} +``` + +### Schema Types + +dynz supports several built-in schema types: + +#### String Schema + +```typescript +import { string } from 'dynz' + +const nameSchema = string() + .min(2, "Name must be at least 2 characters") + .max(50, "Name must not exceed 50 characters") + .regex(/^[a-zA-Z\s]+$/, "Name can only contain letters and spaces") + +const emailSchema = string() + .email("Please enter a valid email address") +``` + +#### Number Schema + +```typescript +import { number } from 'dynz' + +const ageSchema = number() + .min(0, "Age must be positive") + .max(120, "Age must be realistic") + +const priceSchema = number() + .positive("Price must be positive") +``` + +#### Object Schema + +```typescript +import { object, string, number } from 'dynz' + +const productSchema = object({ + name: string().min(1), + price: number().positive(), + description: string().optional() +}) +``` + +#### Array Schema + +```typescript +import { array, string } from 'dynz' + +const tagsSchema = array(string().min(1)) + .min(1, "At least one tag is required") + .max(5, "Maximum 5 tags allowed") +``` + +### Conditional Validation + +dynz excels at dynamic, conditional validation based on other field values: + +```typescript +import { object, string, conditions } from 'dynz' + +const userSchema = object({ + accountType: string().oneOf(['personal', 'business']), + + // Only required if accountType is 'business' + companyName: string() + .requiredIf(conditions.equals('accountType', 'business')) + .min(2), + + // Different validation rules based on account type + email: string() + .email() + .ruleIf( + conditions.equals('accountType', 'business'), + string().regex(/@company\.com$/, "Business accounts must use company email") + ) +}) +``` + +### Framework Integration + +#### Vue.js with VeeValidate + + + Install the VeeValidate integration: `pnpm add @dynz/veevalidate` + + +```typescript +import { useForm } from 'vee-validate' +import { toDynzValidator } from '@dynz/veevalidate' +import { object, string } from 'dynz' + +const schema = object({ + username: string().min(3), + password: string().min(8) +}) + +const { defineField, handleSubmit, errors } = useForm({ + validationSchema: toDynzValidator(schema) +}) + +const [username, usernameAttrs] = defineField('username') +const [password, passwordAttrs] = defineField('password') +``` + +#### React (with custom hook) + +```typescript +import { useState } from 'react' +import { validate } from 'dynz' + +function useFormValidation(schema) { + const [errors, setErrors] = useState({}) + + const validateField = (name, value, allValues) => { + const result = validate(schema, { ...allValues, [name]: value }) + if (!result.success) { + setErrors(prev => ({ + ...prev, + [name]: result.errors[name]?.[0] + })) + } else { + setErrors(prev => { + const newErrors = { ...prev } + delete newErrors[name] + return newErrors + }) + } + } + + return { errors, validateField } +} +``` + +## Advanced Features + +### Private Field Masking + +Mark sensitive fields as private to automatically mask them in validation results: + +```typescript +import { object, string } from 'dynz' + +const userSchema = object({ + username: string(), + password: string().private(), // Will be masked in results + confirmPassword: string().private() +}) +``` + +### Cross-Field Validation + +Reference other fields in your validation rules: + +```typescript +import { object, string, conditions } from 'dynz' + +const signupSchema = object({ + password: string().min(8), + confirmPassword: string() + .equals(conditions.ref('password'), "Passwords must match") +}) +``` + +### Custom Rules + +Create your own validation rules: + +```typescript +import { string, customRule } from 'dynz' + +const strongPasswordRule = customRule((value) => { + const hasUppercase = /[A-Z]/.test(value) + const hasLowercase = /[a-z]/.test(value) + const hasNumbers = /\d/.test(value) + const hasSpecialChar = /[!@#$%^&*]/.test(value) + + return hasUppercase && hasLowercase && hasNumbers && hasSpecialChar +}, "Password must contain uppercase, lowercase, numbers, and special characters") + +const passwordSchema = string() + .min(8) + .custom(strongPasswordRule) +``` + +## Next Steps + +Now that you understand the basics, explore more advanced topics: + +- **[API Reference](/docs/api)** - Complete API documentation +- **[Examples](/docs/examples)** - Real-world usage examples +- **[Framework Integrations](/docs/integrations)** - Vue, React, and other framework guides + + + Check out the [examples directory](https://github.com/your-username/dynz/tree/main/examples) in the repository for complete working examples with Vue.js and Next.js. + \ No newline at end of file diff --git a/docs/content/docs/introduction.mdx b/docs/content/docs/introduction.mdx index 03a73ef..38e8aa1 100644 --- a/docs/content/docs/introduction.mdx +++ b/docs/content/docs/introduction.mdx @@ -8,7 +8,7 @@ import { Callout } from 'fumadocs-ui/components/callout'; # Introduction -dynz is a TypeScript schema validation that provides serializable type-safe validation with dynamic schemas. It is perfect for creating complex +`dynz` is a TypeScript schema validator that provides serializable type-safe validation with dynamic schemas. It is perfect for creating complex schemas that power your form logic. ```typescript @@ -25,7 +25,7 @@ const schema = object({ }), parentalApproval: boolean({ rules: [equals(true)], - // field is only included when age is under 18 + // `parentalApproval` field is only included when age is under 18 included: lt('age', 18) }) } diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx index 4e98c37..325d08e 100644 --- a/docs/src/app/layout.tsx +++ b/docs/src/app/layout.tsx @@ -1,14 +1,18 @@ import "@/app/global.css"; import { RootProvider } from "fumadocs-ui/provider"; -import { Inter } from "next/font/google"; +// import { Inter } from "next/font/google"; -const inter = Inter({ - subsets: ["latin"], -}); +// const inter = Inter({ +// subsets: ["latin"], +// }); export default function Layout({ children }: LayoutProps<"/">) { return ( - + {children} diff --git a/examples/example/src/index.ts b/examples/example/src/index.ts index 1f5ef50..ac3269c 100644 --- a/examples/example/src/index.ts +++ b/examples/example/src/index.ts @@ -1,13 +1,20 @@ -import { conditional, email, eq, matches, min, object, options, regex, string } from "dynz"; +import { boolean, number, SchemaValues, string, tuple, validate } from "dynz"; + +const foo = tuple({ + schema: [ + string(), + boolean(), + tuple({ + schema: [string(), boolean()], + }), + ], +}); -// const foo = object({ -// fields: { -// password: string(), -// confirmPassword: string({ -// rules: [equals(ref('password'))] -// }) -// } -// }) +const f = validate(foo, undefined, ["foo", true, 123]); + +if (f.success) { + const a = f.values[2][1]; +} // const schema = object({ // fields: { @@ -29,33 +36,33 @@ import { conditional, email, eq, matches, min, object, options, regex, string } // console.log(result.values); // ✅ Type-safe access // } -const schema = object({ - fields: { - accountType: options({ - options: ["personal", "business"], - }), +// const schema = object({ +// fields: { +// accountType: options({ +// options: ["personal", "business"], +// }), - // Only included if accountType is 'business' - companyName: string({ - rules: [min(2)], - required: matches("email", "@gmail.com$"), - included: eq("accountType", "business"), - }), +// // Only included if accountType is 'business' +// companyName: string({ +// rules: [min(2)], +// required: matches("email", "@gmail.com$"), +// included: eq("accountType", "business"), +// }), - email: string({ - rules: [ - email(), - conditional({ - // Different validation rules based on account type - when: eq("accountType", "business"), - then: regex("@company.com$", "Business accounts must use company email"), - }), - ], - }), - }, -}); +// email: string({ +// rules: [ +// email(), +// conditional({ +// // Different validation rules based on account type +// when: eq("accountType", "business"), +// then: regex("@company.com$", "Business accounts must use company email"), +// }), +// ], +// }), +// }, +// }); -console.log(JSON.stringify(schema, undefined, 2)); +// console.log(JSON.stringify(schema, undefined, 2)); // // Validate data // const result = validate(schema, undefined, { diff --git a/examples/next-example/.next/trace b/examples/next-example/.next/trace index 7867007..380f937 100644 --- a/examples/next-example/.next/trace +++ b/examples/next-example/.next/trace @@ -1 +1,2 @@ -[{"name":"generate-buildid","duration":95,"timestamp":2950126648760,"id":4,"parentId":1,"tags":{},"startTime":1757343220433,"traceId":"4b8621af667f44c3"},{"name":"load-custom-routes","duration":148,"timestamp":2950126648894,"id":5,"parentId":1,"tags":{},"startTime":1757343220433,"traceId":"4b8621af667f44c3"},{"name":"create-dist-dir","duration":111,"timestamp":2950126673772,"id":6,"parentId":1,"tags":{},"startTime":1757343220458,"traceId":"4b8621af667f44c3"},{"name":"create-pages-mapping","duration":90,"timestamp":2950126679210,"id":7,"parentId":1,"tags":{},"startTime":1757343220464,"traceId":"4b8621af667f44c3"},{"name":"collect-app-paths","duration":1103,"timestamp":2950126679315,"id":8,"parentId":1,"tags":{},"startTime":1757343220464,"traceId":"4b8621af667f44c3"},{"name":"create-app-mapping","duration":1239,"timestamp":2950126680435,"id":9,"parentId":1,"tags":{},"startTime":1757343220465,"traceId":"4b8621af667f44c3"},{"name":"public-dir-conflict-check","duration":185,"timestamp":2950126681859,"id":10,"parentId":1,"tags":{},"startTime":1757343220466,"traceId":"4b8621af667f44c3"},{"name":"generate-routes-manifest","duration":1005,"timestamp":2950126682140,"id":11,"parentId":1,"tags":{},"startTime":1757343220467,"traceId":"4b8621af667f44c3"},{"name":"create-entrypoints","duration":6932,"timestamp":2950126710668,"id":14,"parentId":1,"tags":{},"startTime":1757343220495,"traceId":"4b8621af667f44c3"},{"name":"generate-webpack-config","duration":147387,"timestamp":2950126717629,"id":15,"parentId":13,"tags":{},"startTime":1757343220502,"traceId":"4b8621af667f44c3"},{"name":"next-trace-entrypoint-plugin","duration":1082,"timestamp":2950126909255,"id":17,"parentId":16,"tags":{},"startTime":1757343220694,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":119447,"timestamp":2950126913001,"id":21,"parentId":18,"tags":{"request":"next/dist/pages/_app"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":151854,"timestamp":2950126913025,"id":25,"parentId":18,"tags":{"request":"next/dist/pages/_document"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":171488,"timestamp":2950126913008,"id":22,"parentId":18,"tags":{"request":"next-route-loader?kind=PAGES&page=%2F_error&preferredRegion=&absolutePagePath=next%2Fdist%2Fpages%2F_error&absoluteAppPath=next%2Fdist%2Fpages%2F_app&absoluteDocumentPath=next%2Fdist%2Fpages%2F_document&middlewareConfigBase64=e30%3D!"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"build-module-tsx","duration":10230,"timestamp":2950127093577,"id":26,"parentId":16,"tags":{"name":"/Users/ruben.van.rooij/git/dynz/examples/next-example/src/app/[locale]/page.tsx","layer":"rsc"},"startTime":1757343220878,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":230457,"timestamp":2950126912989,"id":20,"parentId":18,"tags":{"request":"next-app-loader?page=%2Ffavicon.ico%2Froute&name=app%2Ffavicon.ico%2Froute&pagePath=private-next-app-dir%2Ffavicon.ico&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=%2Ffavicon.ico&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":243637,"timestamp":2950126912764,"id":19,"parentId":18,"tags":{"request":"next-app-loader?page=%2F_not-found%2Fpage&name=app%2F_not-found%2Fpage&pagePath=next%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=next%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":243399,"timestamp":2950126913013,"id":23,"parentId":18,"tags":{"request":"next-app-loader?page=%2F%5Blocale%5D%2Fpage&name=app%2F%5Blocale%5D%2Fpage&pagePath=private-next-app-dir%2F%5Blocale%5D%2Fpage.tsx&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=%2F%5Blocale%5D%2Fpage&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":243396,"timestamp":2950126913019,"id":24,"parentId":18,"tags":{"request":"next-app-loader?page=%2Fpage&name=app%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=%2Fpage&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"build-module-tsx","duration":8491,"timestamp":2950127188643,"id":43,"parentId":16,"tags":{"name":"/Users/ruben.van.rooij/git/dynz/examples/next-example/src/app/[locale]/page.tsx","layer":"ssr"},"startTime":1757343220973,"traceId":"4b8621af667f44c3"},{"name":"make","duration":424140,"timestamp":2950126912648,"id":18,"parentId":16,"tags":{},"startTime":1757343220697,"traceId":"4b8621af667f44c3"},{"name":"get-entries","duration":276,"timestamp":2950127337427,"id":45,"parentId":44,"tags":{},"startTime":1757343221122,"traceId":"4b8621af667f44c3"},{"name":"node-file-trace-plugin","duration":38072,"timestamp":2950127339264,"id":46,"parentId":44,"tags":{"traceEntryCount":"10"},"startTime":1757343221124,"traceId":"4b8621af667f44c3"},{"name":"collect-traced-files","duration":213,"timestamp":2950127377348,"id":47,"parentId":44,"tags":{},"startTime":1757343221162,"traceId":"4b8621af667f44c3"},{"name":"finish-modules","duration":40224,"timestamp":2950127337340,"id":44,"parentId":17,"tags":{},"startTime":1757343221122,"traceId":"4b8621af667f44c3"},{"name":"chunk-graph","duration":6551,"timestamp":2950127395734,"id":49,"parentId":48,"tags":{},"startTime":1757343221180,"traceId":"4b8621af667f44c3"},{"name":"optimize-modules","duration":11,"timestamp":2950127402351,"id":51,"parentId":48,"tags":{},"startTime":1757343221187,"traceId":"4b8621af667f44c3"},{"name":"optimize-chunks","duration":6890,"timestamp":2950127402400,"id":52,"parentId":48,"tags":{},"startTime":1757343221187,"traceId":"4b8621af667f44c3"},{"name":"optimize-tree","duration":72,"timestamp":2950127409344,"id":53,"parentId":48,"tags":{},"startTime":1757343221194,"traceId":"4b8621af667f44c3"},{"name":"optimize-chunk-modules","duration":8709,"timestamp":2950127409459,"id":54,"parentId":48,"tags":{},"startTime":1757343221194,"traceId":"4b8621af667f44c3"},{"name":"optimize","duration":15893,"timestamp":2950127402327,"id":50,"parentId":48,"tags":{},"startTime":1757343221187,"traceId":"4b8621af667f44c3"},{"name":"module-hash","duration":10695,"timestamp":2950127426530,"id":55,"parentId":48,"tags":{},"startTime":1757343221211,"traceId":"4b8621af667f44c3"},{"name":"code-generation","duration":132295,"timestamp":2950127437268,"id":56,"parentId":48,"tags":{},"startTime":1757343221222,"traceId":"4b8621af667f44c3"},{"name":"hash","duration":3628,"timestamp":2950127571858,"id":57,"parentId":48,"tags":{},"startTime":1757343221356,"traceId":"4b8621af667f44c3"},{"name":"code-generation-jobs","duration":120,"timestamp":2950127575485,"id":58,"parentId":48,"tags":{},"startTime":1757343221360,"traceId":"4b8621af667f44c3"},{"name":"module-assets","duration":210,"timestamp":2950127575587,"id":59,"parentId":48,"tags":{},"startTime":1757343221360,"traceId":"4b8621af667f44c3"},{"name":"create-chunk-assets","duration":2574,"timestamp":2950127575802,"id":60,"parentId":48,"tags":{},"startTime":1757343221360,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":14004,"timestamp":2950127588370,"id":63,"parentId":61,"tags":{"name":"../app/favicon.ico/route.js","cache":"HIT"},"startTime":1757343221373,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":13973,"timestamp":2950127588413,"id":64,"parentId":61,"tags":{"name":"../pages/_app.js","cache":"HIT"},"startTime":1757343221373,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":13969,"timestamp":2950127588419,"id":65,"parentId":61,"tags":{"name":"../pages/_error.js","cache":"HIT"},"startTime":1757343221373,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":2919,"timestamp":2950127599470,"id":68,"parentId":61,"tags":{"name":"../pages/_document.js","cache":"HIT"},"startTime":1757343221384,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":2906,"timestamp":2950127599484,"id":69,"parentId":61,"tags":{"name":"../webpack-runtime.js","cache":"HIT"},"startTime":1757343221384,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":2902,"timestamp":2950127599488,"id":70,"parentId":61,"tags":{"name":"368.js","cache":"HIT"},"startTime":1757343221384,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":2900,"timestamp":2950127599492,"id":71,"parentId":61,"tags":{"name":"857.js","cache":"HIT"},"startTime":1757343221384,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":246,"timestamp":2950127602146,"id":74,"parentId":61,"tags":{"name":"256.js","cache":"HIT"},"startTime":1757343221387,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":28250,"timestamp":2950127586771,"id":62,"parentId":61,"tags":{"name":"../app/_not-found/page.js","cache":"MISS"},"startTime":1757343221371,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":15825,"timestamp":2950127599222,"id":67,"parentId":61,"tags":{"name":"../app/page.js","cache":"MISS"},"startTime":1757343221384,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":26343,"timestamp":2950127602152,"id":75,"parentId":61,"tags":{"name":"360.js","cache":"MISS"},"startTime":1757343221387,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":37121,"timestamp":2950127599497,"id":72,"parentId":61,"tags":{"name":"979.js","cache":"MISS"},"startTime":1757343221384,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":94140,"timestamp":2950127588422,"id":66,"parentId":61,"tags":{"name":"../app/[locale]/page.js","cache":"MISS"},"startTime":1757343221373,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":94507,"timestamp":2950127600211,"id":73,"parentId":61,"tags":{"name":"498.js","cache":"MISS"},"startTime":1757343221385,"traceId":"4b8621af667f44c3"},{"name":"minify-webpack-plugin-optimize","duration":113084,"timestamp":2950127581649,"id":61,"parentId":16,"tags":{"compilationName":"server","mangle":"true"},"startTime":1757343221366,"traceId":"4b8621af667f44c3"},{"name":"css-minimizer-plugin","duration":98,"timestamp":2950127694836,"id":76,"parentId":16,"tags":{},"startTime":1757343221479,"traceId":"4b8621af667f44c3"},{"name":"create-trace-assets","duration":770,"timestamp":2950127695048,"id":77,"parentId":17,"tags":{},"startTime":1757343221480,"traceId":"4b8621af667f44c3"},{"name":"seal","duration":311893,"timestamp":2950127387393,"id":48,"parentId":16,"tags":{},"startTime":1757343221172,"traceId":"4b8621af667f44c3"},{"name":"webpack-compilation","duration":795134,"timestamp":2950126908414,"id":16,"parentId":13,"tags":{"name":"server"},"startTime":1757343220693,"traceId":"4b8621af667f44c3"},{"name":"emit","duration":3755,"timestamp":2950127703717,"id":78,"parentId":13,"tags":{},"startTime":1757343221488,"traceId":"4b8621af667f44c3"},{"name":"webpack-close","duration":137954,"timestamp":2950127708156,"id":79,"parentId":13,"tags":{"name":"server"},"startTime":1757343221493,"traceId":"4b8621af667f44c3"},{"name":"webpack-generate-error-stats","duration":1185,"timestamp":2950127846150,"id":80,"parentId":79,"tags":{},"startTime":1757343221631,"traceId":"4b8621af667f44c3"},{"name":"make","duration":135,"timestamp":2950127853458,"id":82,"parentId":81,"tags":{},"startTime":1757343221638,"traceId":"4b8621af667f44c3"},{"name":"chunk-graph","duration":24,"timestamp":2950127853988,"id":84,"parentId":83,"tags":{},"startTime":1757343221638,"traceId":"4b8621af667f44c3"},{"name":"optimize-modules","duration":3,"timestamp":2950127854041,"id":86,"parentId":83,"tags":{},"startTime":1757343221638,"traceId":"4b8621af667f44c3"},{"name":"optimize-chunks","duration":34,"timestamp":2950127854073,"id":87,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"optimize-tree","duration":3,"timestamp":2950127854126,"id":88,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"optimize-chunk-modules","duration":27,"timestamp":2950127854160,"id":89,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"optimize","duration":178,"timestamp":2950127854026,"id":85,"parentId":83,"tags":{},"startTime":1757343221638,"traceId":"4b8621af667f44c3"},{"name":"module-hash","duration":8,"timestamp":2950127854292,"id":90,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"code-generation","duration":6,"timestamp":2950127854305,"id":91,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"hash","duration":27,"timestamp":2950127854340,"id":92,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"code-generation-jobs","duration":15,"timestamp":2950127854368,"id":93,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"module-assets","duration":7,"timestamp":2950127854380,"id":94,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"create-chunk-assets","duration":7,"timestamp":2950127854391,"id":95,"parentId":83,"tags":{},"startTime":1757343221639,"traceId":"4b8621af667f44c3"},{"name":"minify-js","duration":20,"timestamp":2950127857152,"id":97,"parentId":96,"tags":{"name":"interception-route-rewrite-manifest.js","cache":"HIT"},"startTime":1757343221642,"traceId":"4b8621af667f44c3"},{"name":"minify-webpack-plugin-optimize","duration":342,"timestamp":2950127856838,"id":96,"parentId":81,"tags":{"compilationName":"edge-server","mangle":"true"},"startTime":1757343221641,"traceId":"4b8621af667f44c3"},{"name":"css-minimizer-plugin","duration":3,"timestamp":2950127857213,"id":98,"parentId":81,"tags":{},"startTime":1757343221642,"traceId":"4b8621af667f44c3"},{"name":"seal","duration":4234,"timestamp":2950127853914,"id":83,"parentId":81,"tags":{},"startTime":1757343221638,"traceId":"4b8621af667f44c3"},{"name":"webpack-compilation","duration":5561,"timestamp":2950127852635,"id":81,"parentId":13,"tags":{"name":"edge-server"},"startTime":1757343221637,"traceId":"4b8621af667f44c3"},{"name":"emit","duration":383,"timestamp":2950127858221,"id":99,"parentId":13,"tags":{},"startTime":1757343221643,"traceId":"4b8621af667f44c3"},{"name":"webpack-close","duration":78,"timestamp":2950127858775,"id":100,"parentId":13,"tags":{"name":"edge-server"},"startTime":1757343221643,"traceId":"4b8621af667f44c3"},{"name":"webpack-generate-error-stats","duration":404,"timestamp":2950127858856,"id":101,"parentId":100,"tags":{},"startTime":1757343221643,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":86148,"timestamp":2950127863890,"id":113,"parentId":103,"tags":{"request":"next-flight-client-entry-loader?server=false!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"build-module-tsx","duration":3997,"timestamp":2950127963352,"id":114,"parentId":102,"tags":{"name":"/Users/ruben.van.rooij/git/dynz/examples/next-example/src/app/[locale]/page.tsx","layer":"app-pages-browser"},"startTime":1757343221748,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":128947,"timestamp":2950127863813,"id":107,"parentId":103,"tags":{"request":"next-client-pages-loader?absolutePagePath=next%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error&page=%2F_not-found%2Fpage!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":135779,"timestamp":2950127863872,"id":108,"parentId":103,"tags":{"request":"next-client-pages-loader?absolutePagePath=next%2Fdist%2Fpages%2F_app&page=%2F_app!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"postcss-process","duration":58160,"timestamp":2950128125822,"id":118,"parentId":117,"tags":{},"startTime":1757343221910,"traceId":"4b8621af667f44c3"},{"name":"postcss-loader","duration":189403,"timestamp":2950127994637,"id":117,"parentId":116,"tags":{},"startTime":1757343221779,"traceId":"4b8621af667f44c3"},{"name":"css-loader","duration":28556,"timestamp":2950128184158,"id":119,"parentId":116,"tags":{"astUsed":"true"},"startTime":1757343221969,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":354195,"timestamp":2950127863879,"id":110,"parentId":103,"tags":{"request":"next-client-pages-loader?absolutePagePath=next%2Fdist%2Fpages%2F_error&page=%2F_error!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"build-module-css","duration":240909,"timestamp":2950127986175,"id":116,"parentId":115,"tags":{"name":"/Users/ruben.van.rooij/git/dynz/examples/next-example/src/app/globals.css.webpack[javascript/auto]!=!/Users/ruben.van.rooij/git/dynz/node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/build/webpack/loaders/css-loader/src/index.js??ruleSet[1].rules[13].oneOf[10].use[2]!/Users/ruben.van.rooij/git/dynz/node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/build/webpack/loaders/postcss-loader/src/index.js??ruleSet[1].rules[13].oneOf[10].use[3]!/Users/ruben.van.rooij/git/dynz/examples/next-example/src/app/globals.css","layer":null},"startTime":1757343221771,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":363996,"timestamp":2950127863876,"id":109,"parentId":103,"tags":{"request":"/Users/ruben.van.rooij/git/dynz/node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/client/router.js"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":364108,"timestamp":2950127863770,"id":104,"parentId":103,"tags":{"request":"./../../node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/client/next.js"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":374895,"timestamp":2950127863808,"id":106,"parentId":103,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-page.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-segment.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fhttp-access-fallback%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Flayout-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fmetadata%2Fasync-metadata.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fmetadata%2Fmetadata-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Frender-from-template-context.js%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"build-module-css","duration":274132,"timestamp":2950127972786,"id":115,"parentId":102,"tags":{"name":"/Users/ruben.van.rooij/git/dynz/examples/next-example/src/app/globals.css","layer":"app-pages-browser"},"startTime":1757343221757,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":384266,"timestamp":2950127863800,"id":105,"parentId":103,"tags":{"request":"./../../node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/client/app-next.js"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"build-module","duration":61,"timestamp":2950128251490,"id":120,"parentId":115,"tags":{},"startTime":1757343222036,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":398568,"timestamp":2950127863882,"id":111,"parentId":103,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp%2Fglobals.css%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext-intl%404.3.4_next%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1__react%4019.1.1_typescript%405.8.3%2Fnode_modules%2Fnext-intl%2Fdist%2Fesm%2Fproduction%2Fshared%2FNextIntlClientProvider.js%22%2C%22ids%22%3A%5B%22default%22%5D%7D&server=false!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"add-entry","duration":400421,"timestamp":2950127863885,"id":112,"parentId":103,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp%2F%5Blocale%5D%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"make","duration":400777,"timestamp":2950127863642,"id":103,"parentId":102,"tags":{},"startTime":1757343221648,"traceId":"4b8621af667f44c3"},{"name":"chunk-graph","duration":2988,"timestamp":2950128275922,"id":122,"parentId":121,"tags":{},"startTime":1757343222060,"traceId":"4b8621af667f44c3"},{"name":"optimize-modules","duration":3,"timestamp":2950128278940,"id":124,"parentId":121,"tags":{},"startTime":1757343222063,"traceId":"4b8621af667f44c3"},{"name":"optimize-chunks","duration":3457,"timestamp":2950128279425,"id":126,"parentId":121,"tags":{},"startTime":1757343222064,"traceId":"4b8621af667f44c3"}] +[{"name":"generate-buildid","duration":97,"timestamp":2961011321272,"id":4,"parentId":1,"tags":{},"startTime":1757354105122,"traceId":"c31c4c1ec2fda4fe"},{"name":"load-custom-routes","duration":149,"timestamp":2961011321411,"id":5,"parentId":1,"tags":{},"startTime":1757354105122,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-dist-dir","duration":109,"timestamp":2961011347244,"id":6,"parentId":1,"tags":{},"startTime":1757354105148,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-pages-mapping","duration":85,"timestamp":2961011354935,"id":7,"parentId":1,"tags":{},"startTime":1757354105156,"traceId":"c31c4c1ec2fda4fe"},{"name":"collect-app-paths","duration":929,"timestamp":2961011355036,"id":8,"parentId":1,"tags":{},"startTime":1757354105156,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-app-mapping","duration":931,"timestamp":2961011355975,"id":9,"parentId":1,"tags":{},"startTime":1757354105157,"traceId":"c31c4c1ec2fda4fe"},{"name":"public-dir-conflict-check","duration":153,"timestamp":2961011357059,"id":10,"parentId":1,"tags":{},"startTime":1757354105158,"traceId":"c31c4c1ec2fda4fe"},{"name":"generate-routes-manifest","duration":972,"timestamp":2961011357292,"id":11,"parentId":1,"tags":{},"startTime":1757354105158,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-entrypoints","duration":8766,"timestamp":2961011398910,"id":14,"parentId":1,"tags":{},"startTime":1757354105200,"traceId":"c31c4c1ec2fda4fe"},{"name":"generate-webpack-config","duration":154510,"timestamp":2961011407706,"id":15,"parentId":13,"tags":{},"startTime":1757354105209,"traceId":"c31c4c1ec2fda4fe"},{"name":"next-trace-entrypoint-plugin","duration":1057,"timestamp":2961011606484,"id":17,"parentId":16,"tags":{},"startTime":1757354105407,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":116584,"timestamp":2961011610200,"id":21,"parentId":18,"tags":{"request":"next/dist/pages/_app"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":158688,"timestamp":2961011610223,"id":25,"parentId":18,"tags":{"request":"next/dist/pages/_document"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":170434,"timestamp":2961011610206,"id":22,"parentId":18,"tags":{"request":"next-route-loader?kind=PAGES&page=%2F_error&preferredRegion=&absolutePagePath=next%2Fdist%2Fpages%2F_error&absoluteAppPath=next%2Fdist%2Fpages%2F_app&absoluteDocumentPath=next%2Fdist%2Fpages%2F_document&middlewareConfigBase64=e30%3D!"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":222752,"timestamp":2961011609962,"id":19,"parentId":18,"tags":{"request":"next-app-loader?page=%2Ffavicon.ico%2Froute&name=app%2Ffavicon.ico%2Froute&pagePath=private-next-app-dir%2Ffavicon.ico&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=%2Ffavicon.ico&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":233766,"timestamp":2961011610188,"id":20,"parentId":18,"tags":{"request":"next-app-loader?page=%2F_not-found%2Fpage&name=app%2F_not-found%2Fpage&pagePath=next%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=next%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":233752,"timestamp":2961011610212,"id":23,"parentId":18,"tags":{"request":"next-app-loader?page=%2F%5Blocale%5D%2Fpage&name=app%2F%5Blocale%5D%2Fpage&pagePath=private-next-app-dir%2F%5Blocale%5D%2Fpage.tsx&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=%2F%5Blocale%5D%2Fpage&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":233749,"timestamp":2961011610218,"id":24,"parentId":18,"tags":{"request":"next-app-loader?page=%2Fpage&name=app%2Fpage&pagePath=private-next-app-dir%2Fpage.tsx&appDir=%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp&appPaths=%2Fpage&pageExtensions=tsx&pageExtensions=ts&pageExtensions=jsx&pageExtensions=js&basePath=&assetPrefix=&nextConfigOutput=&nextConfigExperimentalUseEarlyImport=&preferredRegion=&middlewareConfig=e30%3D!"},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"make","duration":414337,"timestamp":2961011609848,"id":18,"parentId":16,"tags":{},"startTime":1757354105411,"traceId":"c31c4c1ec2fda4fe"},{"name":"get-entries","duration":221,"timestamp":2961012024834,"id":43,"parentId":42,"tags":{},"startTime":1757354105826,"traceId":"c31c4c1ec2fda4fe"},{"name":"node-file-trace-plugin","duration":40148,"timestamp":2961012026669,"id":44,"parentId":42,"tags":{"traceEntryCount":"10"},"startTime":1757354105828,"traceId":"c31c4c1ec2fda4fe"},{"name":"collect-traced-files","duration":202,"timestamp":2961012066828,"id":45,"parentId":42,"tags":{},"startTime":1757354105868,"traceId":"c31c4c1ec2fda4fe"},{"name":"finish-modules","duration":42287,"timestamp":2961012024745,"id":42,"parentId":17,"tags":{},"startTime":1757354105826,"traceId":"c31c4c1ec2fda4fe"},{"name":"chunk-graph","duration":6552,"timestamp":2961012087020,"id":47,"parentId":46,"tags":{},"startTime":1757354105888,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-modules","duration":11,"timestamp":2961012093645,"id":49,"parentId":46,"tags":{},"startTime":1757354105895,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-chunks","duration":5730,"timestamp":2961012093772,"id":50,"parentId":46,"tags":{},"startTime":1757354105895,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-tree","duration":65,"timestamp":2961012099552,"id":51,"parentId":46,"tags":{},"startTime":1757354105900,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-chunk-modules","duration":9671,"timestamp":2961012099658,"id":52,"parentId":46,"tags":{},"startTime":1757354105901,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize","duration":15763,"timestamp":2961012093620,"id":48,"parentId":46,"tags":{},"startTime":1757354105894,"traceId":"c31c4c1ec2fda4fe"},{"name":"module-hash","duration":10752,"timestamp":2961012118298,"id":53,"parentId":46,"tags":{},"startTime":1757354105919,"traceId":"c31c4c1ec2fda4fe"},{"name":"code-generation","duration":3052,"timestamp":2961012129097,"id":54,"parentId":46,"tags":{},"startTime":1757354105930,"traceId":"c31c4c1ec2fda4fe"},{"name":"hash","duration":3853,"timestamp":2961012134243,"id":55,"parentId":46,"tags":{},"startTime":1757354105935,"traceId":"c31c4c1ec2fda4fe"},{"name":"code-generation-jobs","duration":136,"timestamp":2961012138095,"id":56,"parentId":46,"tags":{},"startTime":1757354105939,"traceId":"c31c4c1ec2fda4fe"},{"name":"module-assets","duration":237,"timestamp":2961012138214,"id":57,"parentId":46,"tags":{},"startTime":1757354105939,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-chunk-assets","duration":2807,"timestamp":2961012138456,"id":58,"parentId":46,"tags":{},"startTime":1757354105939,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":7325,"timestamp":2961012147069,"id":60,"parentId":59,"tags":{"name":"../app/favicon.ico/route.js","cache":"HIT"},"startTime":1757354105948,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":5849,"timestamp":2961012148554,"id":62,"parentId":59,"tags":{"name":"../pages/_app.js","cache":"HIT"},"startTime":1757354105949,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":5841,"timestamp":2961012148563,"id":63,"parentId":59,"tags":{"name":"../pages/_error.js","cache":"HIT"},"startTime":1757354105949,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":3773,"timestamp":2961012150633,"id":66,"parentId":59,"tags":{"name":"../pages/_document.js","cache":"HIT"},"startTime":1757354105952,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":3768,"timestamp":2961012150639,"id":67,"parentId":59,"tags":{"name":"../webpack-runtime.js","cache":"HIT"},"startTime":1757354105952,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":3765,"timestamp":2961012150642,"id":68,"parentId":59,"tags":{"name":"368.js","cache":"HIT"},"startTime":1757354105952,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":3763,"timestamp":2961012150646,"id":69,"parentId":59,"tags":{"name":"857.js","cache":"HIT"},"startTime":1757354105952,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":260,"timestamp":2961012154150,"id":72,"parentId":59,"tags":{"name":"256.js","cache":"HIT"},"startTime":1757354105955,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":18411,"timestamp":2961012147152,"id":61,"parentId":59,"tags":{"name":"../app/_not-found/page.js","cache":"MISS"},"startTime":1757354105948,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":15153,"timestamp":2961012150441,"id":65,"parentId":59,"tags":{"name":"../app/page.js","cache":"MISS"},"startTime":1757354105951,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":24827,"timestamp":2961012154168,"id":73,"parentId":59,"tags":{"name":"360.js","cache":"MISS"},"startTime":1757354105955,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":39724,"timestamp":2961012150650,"id":70,"parentId":59,"tags":{"name":"979.js","cache":"MISS"},"startTime":1757354105952,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":90480,"timestamp":2961012148567,"id":64,"parentId":59,"tags":{"name":"../app/[locale]/page.js","cache":"MISS"},"startTime":1757354105949,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":98944,"timestamp":2961012152061,"id":71,"parentId":59,"tags":{"name":"498.js","cache":"MISS"},"startTime":1757354105953,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-webpack-plugin-optimize","duration":108405,"timestamp":2961012142617,"id":59,"parentId":16,"tags":{"compilationName":"server","mangle":"true"},"startTime":1757354105943,"traceId":"c31c4c1ec2fda4fe"},{"name":"css-minimizer-plugin","duration":100,"timestamp":2961012251125,"id":74,"parentId":16,"tags":{},"startTime":1757354106052,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-trace-assets","duration":806,"timestamp":2961012251330,"id":75,"parentId":17,"tags":{},"startTime":1757354106052,"traceId":"c31c4c1ec2fda4fe"},{"name":"seal","duration":177323,"timestamp":2961012078478,"id":46,"parentId":16,"tags":{},"startTime":1757354105879,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-compilation","duration":654473,"timestamp":2961011605629,"id":16,"parentId":13,"tags":{"name":"server"},"startTime":1757354105406,"traceId":"c31c4c1ec2fda4fe"},{"name":"emit","duration":3745,"timestamp":2961012260301,"id":76,"parentId":13,"tags":{},"startTime":1757354106061,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-close","duration":43546,"timestamp":2961012264788,"id":77,"parentId":13,"tags":{"name":"server"},"startTime":1757354106066,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-generate-error-stats","duration":1347,"timestamp":2961012308373,"id":78,"parentId":77,"tags":{},"startTime":1757354106109,"traceId":"c31c4c1ec2fda4fe"},{"name":"make","duration":88,"timestamp":2961012315618,"id":80,"parentId":79,"tags":{},"startTime":1757354106116,"traceId":"c31c4c1ec2fda4fe"},{"name":"chunk-graph","duration":24,"timestamp":2961012316019,"id":82,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-modules","duration":3,"timestamp":2961012316064,"id":84,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-chunks","duration":29,"timestamp":2961012316095,"id":85,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-tree","duration":10,"timestamp":2961012316141,"id":86,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-chunk-modules","duration":24,"timestamp":2961012316182,"id":87,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize","duration":167,"timestamp":2961012316053,"id":83,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"module-hash","duration":8,"timestamp":2961012316307,"id":88,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"code-generation","duration":6,"timestamp":2961012316320,"id":89,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"hash","duration":24,"timestamp":2961012316350,"id":90,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"code-generation-jobs","duration":30,"timestamp":2961012316375,"id":91,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"module-assets","duration":9,"timestamp":2961012316401,"id":92,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-chunk-assets","duration":7,"timestamp":2961012316413,"id":93,"parentId":81,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":17,"timestamp":2961012318152,"id":95,"parentId":94,"tags":{"name":"interception-route-rewrite-manifest.js","cache":"HIT"},"startTime":1757354106119,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-webpack-plugin-optimize","duration":414,"timestamp":2961012317761,"id":94,"parentId":79,"tags":{"compilationName":"edge-server","mangle":"true"},"startTime":1757354106119,"traceId":"c31c4c1ec2fda4fe"},{"name":"css-minimizer-plugin","duration":3,"timestamp":2961012318215,"id":96,"parentId":79,"tags":{},"startTime":1757354106119,"traceId":"c31c4c1ec2fda4fe"},{"name":"seal","duration":3238,"timestamp":2961012315948,"id":81,"parentId":79,"tags":{},"startTime":1757354106117,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-compilation","duration":4431,"timestamp":2961012314801,"id":79,"parentId":13,"tags":{"name":"edge-server"},"startTime":1757354106116,"traceId":"c31c4c1ec2fda4fe"},{"name":"emit","duration":339,"timestamp":2961012319256,"id":97,"parentId":13,"tags":{},"startTime":1757354106120,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-close","duration":83,"timestamp":2961012319777,"id":98,"parentId":13,"tags":{"name":"edge-server"},"startTime":1757354106121,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-generate-error-stats","duration":269,"timestamp":2961012319864,"id":99,"parentId":98,"tags":{},"startTime":1757354106121,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":72754,"timestamp":2961012324337,"id":111,"parentId":101,"tags":{"request":"next-flight-client-entry-loader?server=false!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":115537,"timestamp":2961012324265,"id":105,"parentId":101,"tags":{"request":"next-client-pages-loader?absolutePagePath=next%2Fdist%2Fclient%2Fcomponents%2Fnot-found-error&page=%2F_not-found%2Fpage!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":130958,"timestamp":2961012324320,"id":106,"parentId":101,"tags":{"request":"next-client-pages-loader?absolutePagePath=next%2Fdist%2Fpages%2F_app&page=%2F_app!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":133180,"timestamp":2961012324327,"id":108,"parentId":101,"tags":{"request":"next-client-pages-loader?absolutePagePath=next%2Fdist%2Fpages%2F_error&page=%2F_error!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":139801,"timestamp":2961012324324,"id":107,"parentId":101,"tags":{"request":"/Users/ruben.van.rooij/git/dynz/node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/client/router.js"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":144970,"timestamp":2961012324231,"id":102,"parentId":101,"tags":{"request":"./../../node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/client/next.js"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":148582,"timestamp":2961012324261,"id":104,"parentId":101,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-page.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fclient-segment.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fhttp-access-fallback%2Ferror-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Flayout-router.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fmetadata%2Fasync-metadata.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Fmetadata%2Fmetadata-boundary.js%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1%2Fnode_modules%2Fnext%2Fdist%2Fclient%2Fcomponents%2Frender-from-template-context.js%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":160360,"timestamp":2961012324253,"id":103,"parentId":101,"tags":{"request":"./../../node_modules/.pnpm/next@15.3.5_react-dom@19.1.1_react@19.1.1__react@19.1.1/node_modules/next/dist/client/app-next.js"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":190753,"timestamp":2961012324330,"id":109,"parentId":101,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp%2Fglobals.css%22%2C%22ids%22%3A%5B%5D%7D&modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fnode_modules%2F.pnpm%2Fnext-intl%404.3.4_next%4015.3.5_react-dom%4019.1.1_react%4019.1.1__react%4019.1.1__react%4019.1.1_typescript%405.8.3%2Fnode_modules%2Fnext-intl%2Fdist%2Fesm%2Fproduction%2Fshared%2FNextIntlClientProvider.js%22%2C%22ids%22%3A%5B%22default%22%5D%7D&server=false!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"add-entry","duration":191861,"timestamp":2961012324333,"id":110,"parentId":101,"tags":{"request":"next-flight-client-entry-loader?modules=%7B%22request%22%3A%22%2FUsers%2Fruben.van.rooij%2Fgit%2Fdynz%2Fexamples%2Fnext-example%2Fsrc%2Fapp%2F%5Blocale%5D%2Fpage.tsx%22%2C%22ids%22%3A%5B%5D%7D&server=false!"},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"make","duration":192142,"timestamp":2961012324122,"id":101,"parentId":100,"tags":{},"startTime":1757354106125,"traceId":"c31c4c1ec2fda4fe"},{"name":"chunk-graph","duration":3410,"timestamp":2961012529629,"id":113,"parentId":112,"tags":{},"startTime":1757354106330,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-modules","duration":4,"timestamp":2961012533070,"id":115,"parentId":112,"tags":{},"startTime":1757354106334,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-chunks","duration":4652,"timestamp":2961012533855,"id":117,"parentId":112,"tags":{},"startTime":1757354106335,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-tree","duration":5,"timestamp":2961012538535,"id":118,"parentId":112,"tags":{},"startTime":1757354106339,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize-chunk-modules","duration":5108,"timestamp":2961012538553,"id":119,"parentId":112,"tags":{},"startTime":1757354106339,"traceId":"c31c4c1ec2fda4fe"},{"name":"optimize","duration":10623,"timestamp":2961012533060,"id":114,"parentId":112,"tags":{},"startTime":1757354106334,"traceId":"c31c4c1ec2fda4fe"},{"name":"module-hash","duration":4816,"timestamp":2961012549718,"id":120,"parentId":112,"tags":{},"startTime":1757354106351,"traceId":"c31c4c1ec2fda4fe"},{"name":"code-generation","duration":944,"timestamp":2961012554562,"id":121,"parentId":112,"tags":{},"startTime":1757354106355,"traceId":"c31c4c1ec2fda4fe"},{"name":"hash","duration":2559,"timestamp":2961012556869,"id":122,"parentId":112,"tags":{},"startTime":1757354106358,"traceId":"c31c4c1ec2fda4fe"},{"name":"code-generation-jobs","duration":147,"timestamp":2961012559427,"id":123,"parentId":112,"tags":{},"startTime":1757354106360,"traceId":"c31c4c1ec2fda4fe"},{"name":"module-assets","duration":90,"timestamp":2961012559561,"id":124,"parentId":112,"tags":{},"startTime":1757354106360,"traceId":"c31c4c1ec2fda4fe"},{"name":"create-chunk-assets","duration":965,"timestamp":2961012559655,"id":125,"parentId":112,"tags":{},"startTime":1757354106361,"traceId":"c31c4c1ec2fda4fe"}] +[{"name":"NextJsBuildManifest-generateClientManifest","duration":733,"timestamp":2961012561529,"id":127,"parentId":100,"tags":{},"startTime":1757354106362,"traceId":"c31c4c1ec2fda4fe"},{"name":"NextJsBuildManifest-createassets","duration":1078,"timestamp":2961012561189,"id":126,"parentId":100,"tags":{},"startTime":1757354106362,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":198,"timestamp":2961012564050,"id":129,"parentId":128,"tags":{"name":"static/chunks/main-ad41ac689df7cb05.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":181,"timestamp":2961012564069,"id":130,"parentId":128,"tags":{"name":"static/chunks/main-app-b008306136c9aa8a.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":176,"timestamp":2961012564075,"id":131,"parentId":128,"tags":{"name":"static/chunks/app/_not-found/page-3c4b55b490798e8e.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":172,"timestamp":2961012564080,"id":132,"parentId":128,"tags":{"name":"static/chunks/pages/_app-4a74985419195a15.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":169,"timestamp":2961012564084,"id":133,"parentId":128,"tags":{"name":"static/chunks/pages/_error-36dbe6b4df21c7b4.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":167,"timestamp":2961012564087,"id":134,"parentId":128,"tags":{"name":"static/chunks/app/layout-a4afaea6b12462d8.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":164,"timestamp":2961012564091,"id":135,"parentId":128,"tags":{"name":"static/chunks/app/[locale]/page-5a6ec397883cba6c.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":161,"timestamp":2961012564094,"id":136,"parentId":128,"tags":{"name":"static/chunks/app/page-9f8d50a03e074da9.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":159,"timestamp":2961012564097,"id":137,"parentId":128,"tags":{"name":"static/chunks/webpack-f571b29ee03141fd.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":157,"timestamp":2961012564100,"id":138,"parentId":128,"tags":{"name":"static/chunks/framework-d6f52249270a7456.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":154,"timestamp":2961012564103,"id":139,"parentId":128,"tags":{"name":"static/chunks/fc0ab130-cdbbc2c44752a813.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":152,"timestamp":2961012564106,"id":140,"parentId":128,"tags":{"name":"static/chunks/281-00741685ba81fe9b.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":149,"timestamp":2961012564110,"id":141,"parentId":128,"tags":{"name":"static/chunks/294-5b1d257cf0a8a5a6.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":147,"timestamp":2961012564113,"id":142,"parentId":128,"tags":{"name":"static/chunks/105-ac4508b78336a076.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":145,"timestamp":2961012564116,"id":143,"parentId":128,"tags":{"name":"server/middleware-react-loadable-manifest.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":40,"timestamp":2961012564221,"id":145,"parentId":128,"tags":{"name":"server/middleware-build-manifest.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":16,"timestamp":2961012564246,"id":147,"parentId":128,"tags":{"name":"server/next-font-manifest.js","cache":"HIT"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":535,"timestamp":2961012564163,"id":144,"parentId":128,"tags":{"name":"static/L26cplQZbJaAG-AcYOt2e/_ssgManifest.js","cache":"MISS"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-js","duration":614,"timestamp":2961012564226,"id":146,"parentId":128,"tags":{"name":"static/L26cplQZbJaAG-AcYOt2e/_buildManifest.js","cache":"MISS"},"startTime":1757354106365,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-webpack-plugin-optimize","duration":2204,"timestamp":2961012562641,"id":128,"parentId":100,"tags":{"compilationName":"client","mangle":"true"},"startTime":1757354106364,"traceId":"c31c4c1ec2fda4fe"},{"name":"minify-css","duration":37,"timestamp":2961012564896,"id":149,"parentId":148,"tags":{"file":"static/css/39dc86cea916b2f3.css","cache":"HIT"},"startTime":1757354106366,"traceId":"c31c4c1ec2fda4fe"},{"name":"css-minimizer-plugin","duration":63,"timestamp":2961012564872,"id":148,"parentId":100,"tags":{},"startTime":1757354106366,"traceId":"c31c4c1ec2fda4fe"},{"name":"seal","duration":46497,"timestamp":2961012522005,"id":112,"parentId":100,"tags":{},"startTime":1757354106323,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-compilation","duration":244983,"timestamp":2961012323557,"id":100,"parentId":13,"tags":{"name":"client"},"startTime":1757354106124,"traceId":"c31c4c1ec2fda4fe"},{"name":"emit","duration":3383,"timestamp":2961012568560,"id":150,"parentId":13,"tags":{},"startTime":1757354106369,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-close","duration":12499,"timestamp":2961012572197,"id":151,"parentId":13,"tags":{"name":"client"},"startTime":1757354106373,"traceId":"c31c4c1ec2fda4fe"},{"name":"webpack-generate-error-stats","duration":527,"timestamp":2961012584708,"id":152,"parentId":151,"tags":{},"startTime":1757354106386,"traceId":"c31c4c1ec2fda4fe"},{"name":"run-webpack-compiler","duration":1186506,"timestamp":2961011398908,"id":13,"parentId":1,"tags":{},"startTime":1757354105200,"traceId":"c31c4c1ec2fda4fe"},{"name":"format-webpack-messages","duration":59,"timestamp":2961012585418,"id":153,"parentId":1,"tags":{},"startTime":1757354106386,"traceId":"c31c4c1ec2fda4fe"},{"name":"verify-and-lint","duration":1281139,"timestamp":2961012590005,"id":156,"parentId":1,"tags":{},"startTime":1757354106391,"traceId":"c31c4c1ec2fda4fe"},{"name":"verify-typescript-setup","duration":2162205,"timestamp":2961012587463,"id":155,"parentId":1,"tags":{},"startTime":1757354106388,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-static-error-page","duration":1812,"timestamp":2961014767443,"id":159,"parentId":158,"tags":{},"startTime":1757354108568,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":821,"timestamp":2961014802974,"id":160,"parentId":158,"tags":{"page":"/_app"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":374,"timestamp":2961014803435,"id":162,"parentId":158,"tags":{"page":"/_document"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":478,"timestamp":2961014803402,"id":161,"parentId":158,"tags":{"page":"/_error"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"is-page-static","duration":215348,"timestamp":2961014814929,"id":168,"parentId":163,"tags":{},"startTime":1757354108616,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":226840,"timestamp":2961014803510,"id":163,"parentId":158,"tags":{"page":"/_not-found"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"is-page-static","duration":224796,"timestamp":2961014814554,"id":167,"parentId":166,"tags":{},"startTime":1757354108615,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":235789,"timestamp":2961014803614,"id":166,"parentId":158,"tags":{"page":"/favicon.ico"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"is-page-static","duration":221176,"timestamp":2961014819821,"id":169,"parentId":164,"tags":{},"startTime":1757354108621,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":237440,"timestamp":2961014803579,"id":164,"parentId":158,"tags":{"page":"/[locale]"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"is-page-static","duration":232403,"timestamp":2961014819922,"id":170,"parentId":165,"tags":{},"startTime":1757354108621,"traceId":"c31c4c1ec2fda4fe"},{"name":"check-page","duration":248768,"timestamp":2961014803598,"id":165,"parentId":158,"tags":{"page":"/"},"startTime":1757354108604,"traceId":"c31c4c1ec2fda4fe"},{"name":"static-check","duration":291744,"timestamp":2961014760653,"id":158,"parentId":1,"tags":{},"startTime":1757354108562,"traceId":"c31c4c1ec2fda4fe"},{"name":"generate-required-server-files","duration":199,"timestamp":2961015052443,"id":172,"parentId":1,"tags":{},"startTime":1757354108853,"traceId":"c31c4c1ec2fda4fe"},{"name":"write-routes-manifest","duration":541,"timestamp":2961015058148,"id":174,"parentId":1,"tags":{},"startTime":1757354108859,"traceId":"c31c4c1ec2fda4fe"},{"name":"load-dotenv","duration":17,"timestamp":2961015126503,"id":177,"parentId":176,"tags":{},"startTime":1757354108927,"traceId":"c31c4c1ec2fda4fe"},{"name":"run-export-path-map","duration":214,"timestamp":2961015439964,"id":178,"parentId":176,"tags":{},"startTime":1757354109241,"traceId":"c31c4c1ec2fda4fe"},{"name":"next-export","duration":747656,"timestamp":2961015126037,"id":176,"parentId":1,"tags":{},"startTime":1757354108927,"traceId":"c31c4c1ec2fda4fe"},{"name":"move-exported-app-not-found-","duration":37,"timestamp":2961015874256,"id":179,"parentId":175,"tags":{},"startTime":1757354109675,"traceId":"c31c4c1ec2fda4fe"},{"name":"move-exported-page","duration":6933,"timestamp":2961015874340,"id":180,"parentId":175,"tags":{},"startTime":1757354109675,"traceId":"c31c4c1ec2fda4fe"},{"name":"static-generation","duration":793871,"timestamp":2961015121899,"id":175,"parentId":1,"tags":{},"startTime":1757354108923,"traceId":"c31c4c1ec2fda4fe"},{"name":"write-routes-manifest","duration":4470,"timestamp":2961015915787,"id":181,"parentId":1,"tags":{},"startTime":1757354109717,"traceId":"c31c4c1ec2fda4fe"},{"name":"node-file-trace-build","duration":3802328,"timestamp":2961015054257,"id":173,"parentId":1,"tags":{"isTurbotrace":"false"},"startTime":1757354108855,"traceId":"c31c4c1ec2fda4fe"},{"name":"apply-include-excludes","duration":275,"timestamp":2961018856601,"id":182,"parentId":1,"tags":{},"startTime":1757354112657,"traceId":"c31c4c1ec2fda4fe"},{"name":"print-tree-view","duration":1343,"timestamp":2961018856946,"id":183,"parentId":1,"tags":{},"startTime":1757354112658,"traceId":"c31c4c1ec2fda4fe"},{"name":"telemetry-flush","duration":14,"timestamp":2961018858294,"id":184,"parentId":1,"tags":{},"startTime":1757354112659,"traceId":"c31c4c1ec2fda4fe"},{"name":"next-build","duration":7645140,"timestamp":2961011213172,"id":1,"tags":{"buildMode":"default","isTurboBuild":"false","version":"15.3.5","has-custom-webpack-config":"true","use-build-worker":"false"},"startTime":1757354105014,"traceId":"c31c4c1ec2fda4fe"}] diff --git a/examples/next-example/src/components/dynz/dynz-form.tsx b/examples/next-example/src/components/dynz/dynz-form.tsx index e122a93..902b630 100644 --- a/examples/next-example/src/components/dynz/dynz-form.tsx +++ b/examples/next-example/src/components/dynz/dynz-form.tsx @@ -49,22 +49,6 @@ export function DynzForm, A extends SchemaValues) { const t = useTranslations(); - // const resolver = dynzResolver( - // schema, - // undefined, - // { - // stripNotIncludedValues: true, - // }, - // { - // messageTransformer: (error: ErrorMessage) => { - // const customErrorMessagePath = `${name}.${error.path.slice(2)}.errors.${error.customCode}`; - // return t.has(customErrorMessagePath) - // ? t(customErrorMessagePath, error as unknown as Record) - // : t(`errors.${error.customCode}`, error as unknown as Record); - // }, - // } - // ); - const methods = useForm({ resolver: dynzResolver( schema, @@ -139,7 +123,12 @@ export function DynzTextInput({ name, description }: BaseInputProps) { - + {description && {description}} diff --git a/packages/dynz/src/resolve.test.ts b/packages/dynz/src/resolve.test.ts index 87b76c3..4f5898b 100644 --- a/packages/dynz/src/resolve.test.ts +++ b/packages/dynz/src/resolve.test.ts @@ -523,7 +523,7 @@ describe("resolve", () => { fields: { user: object({ fields: { - name: string({ default: "Unknown" }), + name: string(), age: number(), contacts: array({ schema: string(), @@ -543,16 +543,6 @@ describe("resolve", () => { }); }); - it("should return default value when value is undefined", () => { - const values = { user: { age: 30 } }; - const result = getNested("$.user.name", schema, values); - - expect(result).toEqual({ - schema: expect.objectContaining({ type: SchemaType.STRING }), - value: "Unknown", - }); - }); - it("should get nested value in array", () => { const values = { user: { contacts: ["email@test.com", "phone"] } }; const result = getNested("$.user.contacts[0]", schema, values); diff --git a/packages/dynz/src/resolve.ts b/packages/dynz/src/resolve.ts index e655c6f..1dc4a79 100644 --- a/packages/dynz/src/resolve.ts +++ b/packages/dynz/src/resolve.ts @@ -352,7 +352,7 @@ export function getNested(path: string, schema: T, value: unkn } return { - value: val === undefined ? acc.schema.default : val, + value: val, schema: acc.schema.schema, }; } @@ -374,7 +374,7 @@ export function getNested(path: string, schema: T, value: unkn } return { - value: val === undefined ? acc.schema.fields[cur]?.default : val, + value: val, schema: childSchema, }; } diff --git a/packages/dynz/src/schema.test.ts b/packages/dynz/src/schema.test.ts index 8562dae..8286dfb 100644 --- a/packages/dynz/src/schema.test.ts +++ b/packages/dynz/src/schema.test.ts @@ -3,9 +3,9 @@ import { equals, max, min } from "./rules"; import { array, boolean, - date, // rules, DEFAULT_DATE_STRING_FORMAT, + date, dateString, file, number, @@ -14,6 +14,7 @@ import { options, required, string, + tuple, } from "./schema"; import { SchemaType } from "./types"; @@ -30,14 +31,12 @@ describe("schema", () => { it("should create string schema with options", () => { const schema = string({ required: true, - default: "hello", rules: [min(3), max(10)], }); expect(schema).toEqual({ type: SchemaType.STRING, required: true, - default: "hello", rules: [ { type: "min", min: 3 }, { type: "max", max: 10 }, @@ -51,7 +50,6 @@ describe("schema", () => { mutable: true, included: false, private: true, - default: "test", }); expect(schema).toEqual({ @@ -60,7 +58,6 @@ describe("schema", () => { mutable: true, included: false, private: true, - default: "test", }); }); }); @@ -77,14 +74,12 @@ describe("schema", () => { it("should create number schema with options", () => { const schema = number({ required: true, - default: 42, rules: [min(0), max(100)], }); expect(schema).toEqual({ type: SchemaType.NUMBER, required: true, - default: 42, rules: [ { type: "min", min: 0 }, { type: "max", max: 100 }, @@ -215,7 +210,6 @@ describe("schema", () => { mutable: true, included: true, private: false, - default: "medium", }); expect(schema).toEqual({ @@ -225,7 +219,6 @@ describe("schema", () => { mutable: true, included: true, private: false, - default: "medium", }); }); }); @@ -357,6 +350,116 @@ describe("schema", () => { }); }); + describe("tuple", () => { + it("should create basic tuple schema with mixed types", () => { + const schema = tuple({ + schema: [string(), number(), boolean()], + }); + + expect(schema).toEqual({ + type: SchemaType.TUPLE, + schema: [{ type: SchemaType.STRING }, { type: SchemaType.NUMBER }, { type: SchemaType.BOOLEAN }], + }); + }); + + it("should create tuple schema with single element", () => { + const schema = tuple({ + schema: [string()], + }); + + expect(schema).toEqual({ + type: SchemaType.TUPLE, + schema: [{ type: SchemaType.STRING }], + }); + }); + + it("should create tuple schema with empty array", () => { + const schema = tuple({ + schema: [], + }); + + expect(schema).toEqual({ + type: SchemaType.TUPLE, + schema: [], + }); + }); + + it("should create tuple schema with complex types", () => { + const schema = tuple({ + schema: [ + string({ required: true }), + number({ rules: [min(0), max(100)] }), + object({ + fields: { + name: string(), + age: number(), + }, + }), + array({ schema: string() }), + ], + }); + + expect(schema.type).toBe(SchemaType.TUPLE); + expect(schema.schema).toHaveLength(4); + expect(schema.schema[0]).toEqual({ type: SchemaType.STRING, required: true }); + expect(schema.schema[1]).toEqual({ + type: SchemaType.NUMBER, + rules: [ + { type: "min", min: 0 }, + { type: "max", max: 100 }, + ], + }); + expect(schema.schema[2].type).toBe(SchemaType.OBJECT); + expect(schema.schema[3].type).toBe(SchemaType.ARRAY); + }); + + it("should create tuple schema with nested tuples", () => { + const schema = tuple({ + schema: [ + string(), + tuple({ + schema: [number(), boolean()], + }), + ], + }); + + expect(schema.type).toBe(SchemaType.TUPLE); + expect(schema.schema).toHaveLength(2); + expect(schema.schema[0].type).toBe(SchemaType.STRING); + expect(schema.schema[1].type).toBe(SchemaType.TUPLE); + expect(schema.schema[1].schema).toEqual([{ type: SchemaType.NUMBER }, { type: SchemaType.BOOLEAN }]); + }); + + it("should create tuple schema with all base properties", () => { + const schema = tuple({ + schema: [string(), number()], + required: false, + mutable: true, + included: true, + private: false, + }); + + expect(schema.type).toBe(SchemaType.TUPLE); + expect(schema.schema).toEqual([{ type: SchemaType.STRING }, { type: SchemaType.NUMBER }]); + expect(schema.required).toBe(false); + expect(schema.mutable).toBe(true); + expect(schema.included).toBe(true); + expect(schema.private).toBe(false); + }); + + it("should create tuple schema with date and date string", () => { + const schema = tuple({ + schema: [date(), dateString({ format: "MM/dd/yyyy" })], + }); + + expect(schema.type).toBe(SchemaType.TUPLE); + expect(schema.schema).toEqual([ + { type: SchemaType.DATE }, + { type: SchemaType.DATE_STRING, format: "MM/dd/yyyy" }, + ]); + }); + }); + describe("date", () => { it("should create basic date schema", () => { const schema = date(); @@ -369,13 +472,11 @@ describe("schema", () => { it("should create date schema with options", () => { const schema = date({ required: true, - default: new Date("2024-01-01"), }); expect(schema).toEqual({ type: SchemaType.DATE, required: true, - default: new Date("2024-01-01"), }); }); @@ -397,7 +498,6 @@ describe("schema", () => { included: true, private: false, coerce: true, - default: new Date("2023-12-25"), }); expect(schema).toEqual({ @@ -407,7 +507,6 @@ describe("schema", () => { included: true, private: false, coerce: true, - default: new Date("2023-12-25"), }); }); @@ -453,14 +552,12 @@ describe("schema", () => { const schema = dateString({ format: "dd-MM-yyyy", required: true, - default: "01-01-2024", }); expect(schema).toEqual({ type: SchemaType.DATE_STRING, format: "dd-MM-yyyy", required: true, - default: "01-01-2024", }); }); @@ -490,13 +587,12 @@ describe("schema", () => { }); it("should override required property", () => { - const baseSchema = number({ required: true, default: 0 }); + const baseSchema = number({ required: true }); const optionalSchema = optional(baseSchema); expect(optionalSchema).toEqual({ type: SchemaType.NUMBER, required: false, - default: 0, }); }); }); @@ -537,7 +633,7 @@ describe("schema", () => { fields: { street: string({ required: true }), city: string({ required: true }), - country: string({ default: "US" }), + country: string({}), }, }), rules: [max(3)], diff --git a/packages/dynz/src/schema.ts b/packages/dynz/src/schema.ts index 5b586d7..ae88d17 100644 --- a/packages/dynz/src/schema.ts +++ b/packages/dynz/src/schema.ts @@ -12,6 +12,7 @@ import { type Schema, SchemaType, type StringSchema, + type TupleSchema, } from "./types"; export function string(): StringSchema; @@ -106,6 +107,15 @@ export const array = , "type">>( + value: A +): Prettify, "type">> => { + return { + ...value, + type: SchemaType.TUPLE, + }; +}; + export function optional(schema: T): T & { required: false } { return { ...schema, diff --git a/packages/dynz/src/types.ts b/packages/dynz/src/types.ts index 26eb76a..c83ff89 100644 --- a/packages/dynz/src/types.ts +++ b/packages/dynz/src/types.ts @@ -5,6 +5,8 @@ export type Prettify = { [K in keyof T]: T[K]; } & {}; +type Flatten = T extends (infer U)[] ? U : T; + export type Filter = T extends [] ? [] : T extends [infer H, ...infer R] @@ -278,6 +280,7 @@ export const SchemaType = { NUMBER: "number", OBJECT: "object", ARRAY: "array", + TUPLE: "tuple", OPTIONS: "options", BOOLEAN: "boolean", FILE: "file", @@ -285,10 +288,9 @@ export const SchemaType = { export type SchemaType = EnumValues; -export type BaseSchema = { +export type BaseSchema = { type: TType; rules?: Array>; - default?: TValue; required?: boolean | Condition; mutable?: boolean | Condition; included?: boolean | Condition; @@ -312,11 +314,7 @@ export type StringRules = | EmailRule | CustomRule | OneOfRule>; -export type StringSchema = BaseSchema< - string, - typeof SchemaType.STRING, - TRule -> & +export type StringSchema = BaseSchema & PrivateSchema & { coerce?: boolean }; /** @@ -335,7 +333,7 @@ export type DateStringRules = export type DateStringSchema< TFormat extends string = string, TRule extends DateStringRules = DateStringRules, -> = BaseSchema & +> = BaseSchema & PrivateSchema & { /* * Unicode Tokens @@ -352,7 +350,6 @@ export type DateStringSchema< */ export type OptionsRules = EqualsRule | CustomRule; export type OptionsSchema = BaseSchema< - TValue, typeof SchemaType.OPTIONS, OptionsRules > & { @@ -364,21 +361,13 @@ export type OptionsSchema = Ba */ // TODO: Add mime type rule export type FileRules = MinRule | MaxRule | MimeTypeRule; -export type FileSchema = BaseSchema< - TValue, - typeof SchemaType.FILE, - FileRules ->; +export type FileSchema = BaseSchema; /** * OBJECT SCHEMA */ export type ObjectRules = CustomRule | MinRule | MaxRule; -export type ObjectSchema> = BaseSchema< - [T] extends [never] ? Record : { [A in keyof T]: SchemaValuesInternal }, - typeof SchemaType.OBJECT, - ObjectRules -> & { +export type ObjectSchema> = BaseSchema & { fields: [T] extends [never] ? Record : T; }; @@ -386,15 +375,19 @@ export type ObjectSchema> = BaseSchema< * ARRAY SCHEMA */ export type ArrayRules = MinRule | MaxRule | CustomRule; -export type ArraySchema = BaseSchema< - [T] extends [never] ? unknown[] : SchemaValuesInternal[], - typeof SchemaType.ARRAY, - ArrayRules -> & { +export type ArraySchema = BaseSchema & { schema: [T] extends [never] ? Schema : T; coerce?: boolean; }; +/** + * TUPLE SCHEMA + */ +export type TupleRules = CustomRule; +export type TupleSchema = BaseSchema & { + schema: [T] extends [never] ? unknown[] : T; +}; + /** * DATE SCHEMA */ @@ -405,7 +398,7 @@ export type DateRules = | BeforeRule | EqualsRule | CustomRule; -export type DateSchema = BaseSchema & { +export type DateSchema = BaseSchema & { coerce?: boolean; }; @@ -419,7 +412,7 @@ export type NumberRules = | EqualsRule | CustomRule | OneOfRule>; -export type NumberSchema = BaseSchema & { +export type NumberSchema = BaseSchema & { coerce?: boolean; }; @@ -427,7 +420,7 @@ export type NumberSchema = BaseSchema | CustomRule; -export type BooleanSchema = BaseSchema & { +export type BooleanSchema = BaseSchema & { coerce?: boolean; }; @@ -437,6 +430,7 @@ export type Schema = | NumberSchema | BooleanSchema | ArraySchema + | TupleSchema | DateStringSchema | OptionsSchema | FileSchema @@ -497,7 +491,9 @@ export type ValueType = T extends typeof Sche ? string | number : T extends typeof SchemaType.FILE ? File - : never; + : T extends typeof SchemaType.TUPLE + ? unknown[] + : never; // === Object Field Categorization === type OptionalFields> = { @@ -514,11 +510,19 @@ type RequiredFields> = { export type ObjectValue> = OptionalFields & RequiredFields; +export type TupleValue = { + [K in keyof T]: T[K] extends Schema ? SchemaValuesInternal : unknown; +}; + export type SchemaValuesInternal = T extends ObjectSchema ? Prettify> : T extends ArraySchema ? MakeOptional>> - : MakeOptional>; + : T extends OptionsSchema + ? MakeOptional> + : T extends TupleSchema + ? MakeOptional> + : MakeOptional>; export type SchemaValues = Prettify>>; /*** diff --git a/packages/dynz/src/validate.test.ts b/packages/dynz/src/validate.test.ts index 2963e8a..622e6ed 100644 --- a/packages/dynz/src/validate.test.ts +++ b/packages/dynz/src/validate.test.ts @@ -14,7 +14,7 @@ import { oneOf, regex, } from "./rules"; -import { array, boolean, date, dateString, file, number, object, options, string } from "./schema"; +import { array, boolean, date, dateString, file, number, object, options, string, tuple } from "./schema"; import { type CustomRuleMap, ErrorCode, SchemaType } from "./types"; import { isArray, @@ -522,6 +522,257 @@ describe("validate", () => { }); }); + describe("tuple validation", () => { + it("should validate basic tuple with correct types", () => { + const schema = tuple({ + schema: [string(), number(), boolean()], + }); + const result = validate(schema, undefined, ["hello", 42, true]); + + expect(result).toEqual({ + success: true, + values: ["hello", 42, true], + }); + }); + + it("should fail when tuple value is not an array", () => { + const schema = tuple({ + schema: [string(), number()], + }); + const result = validate(schema, undefined, "not an array"); + + expect(result).toEqual({ + success: false, + errors: [ + expect.objectContaining({ + code: ErrorCode.TYPE, + expectedType: SchemaType.TUPLE, + }), + ], + }); + }); + + it("should fail when tuple has wrong type at specific position", () => { + const schema = tuple({ + schema: [string(), number(), boolean()], + }); + const result = validate(schema, undefined, ["hello", "not a number", true]); + + expect(result).toEqual({ + success: false, + errors: [ + expect.objectContaining({ + code: ErrorCode.TYPE, + path: "$.[1]", + expectedType: SchemaType.NUMBER, + }), + ], + }); + }); + + it("should fail when tuple has too many elements", () => { + const schema = tuple({ + schema: [string(), number()], + }); + const result = validate(schema, undefined, ["hello", 42, "extra"]); + + expect(result).toEqual({ + success: false, + errors: [ + expect.objectContaining({ + code: ErrorCode.TYPE, + path: "$.[2]", + expectedType: SchemaType.TUPLE, + }), + ], + }); + }); + + it("should validate tuple with fewer elements than schema length", () => { + const schema = tuple({ + schema: [string(), number({ required: false }), boolean({ required: false })], + }); + const result = validate(schema, undefined, ["hello"]); + + expect(result.success).toBe(true); + }); + + it("should validate empty tuple", () => { + const schema = tuple({ + schema: [], + }); + const result = validate(schema, undefined, []); + + expect(result).toEqual({ + success: true, + values: [], + }); + }); + + it("should fail when empty tuple schema receives non-empty array", () => { + const schema = tuple({ + schema: [], + }); + const result = validate(schema, undefined, ["unexpected"]); + + expect(result).toEqual({ + success: false, + errors: [ + expect.objectContaining({ + code: ErrorCode.TYPE, + path: "$.[0]", + expectedType: SchemaType.TUPLE, + }), + ], + }); + }); + + it("should validate nested tuple", () => { + const schema = tuple({ + schema: [ + string(), + tuple({ + schema: [number(), boolean()], + }), + ], + }); + const result = validate(schema, undefined, ["hello", [42, true]]); + + expect(result).toEqual({ + success: true, + values: ["hello", [42, true]], + }); + }); + + it("should fail validation for nested tuple with wrong inner type", () => { + const schema = tuple({ + schema: [ + string(), + tuple({ + schema: [number(), boolean()], + }), + ], + }); + const result = validate(schema, undefined, ["hello", [42, "not boolean"]]); + + expect(result).toEqual({ + success: false, + errors: [ + expect.objectContaining({ + code: ErrorCode.TYPE, + path: "$.[1].[1]", + expectedType: SchemaType.BOOLEAN, + }), + ], + }); + }); + + it("should validate tuple with complex nested schemas", () => { + const schema = tuple({ + schema: [ + string(), + object({ + fields: { + id: number(), + name: string(), + }, + }), + array({ schema: string() }), + ], + }); + + const result = validate(schema, undefined, ["hello", { id: 1, name: "John" }, ["tag1", "tag2"]]); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.values[0]).toBe("hello"); + expect(result.values[1]).toEqual({ id: 1, name: "John" }); + expect(result.values[2]).toEqual(["tag1", "tag2"]); + } + }); + + it("should validate tuple with rules on individual elements", () => { + const schema = tuple({ + schema: [string({ rules: [min(3), max(10)] }), number({ rules: [min(0), max(100)] }), boolean()], + }); + + const validResult = validate(schema, undefined, ["hello", 50, true]); + expect(validResult.success).toBe(true); + + const invalidStringResult = validate(schema, undefined, ["hi", 50, true]); + expect(invalidStringResult.success).toBe(false); + + const invalidNumberResult = validate(schema, undefined, ["hello", 150, true]); + expect(invalidNumberResult.success).toBe(false); + }); + + it("should validate tuple with required and optional elements", () => { + const schema = tuple({ + schema: [string({ required: true }), number({ required: false }), boolean({ required: false })], + }); + + const fullResult = validate(schema, undefined, ["hello", 42, true]); + expect(fullResult.success).toBe(true); + + const partialResult = validate(schema, undefined, ["hello"]); + expect(partialResult.success).toBe(true); + + // For tuples, if we provide an empty array, there's no element at position 0 + // so it should actually pass because undefined is processed for the required field + // Let me test with undefined at the first position instead + const undefinedRequiredResult = validate(schema, undefined, [undefined, 42]); + expect(undefinedRequiredResult.success).toBe(false); + }); + + it("should validate tuple with date elements", () => { + const schema = tuple({ + schema: [date(), dateString({ format: "MM/dd/yyyy" })], + }); + + const testDate = new Date("2023-12-25"); + const result = validate(schema, undefined, [testDate, "12/25/2023"]); + + expect(result.success).toBe(true); + if (result.success) { + expect(result.values[0]).toBe(testDate); + expect(result.values[1]).toBe("12/25/2023"); + } + }); + + it("should validate tuple with coercion enabled on elements", () => { + const schema = tuple({ + schema: [string({ coerce: true }), number({ coerce: true }), boolean({ coerce: true })], + }); + + const result = validate(schema, undefined, [123, "456", "true"]); + expect(result.success).toBe(true); + if (result.success) { + expect(result.values[0]).toBe("123"); + expect(result.values[1]).toBe(456); + expect(result.values[2]).toBe(true); + } + }); + + it("should handle mutability validation for tuples", () => { + const originalTuple = ["original", 42]; + const changedTuple = ["changed", 42]; + + // Test that the tuple itself can be immutable + const tupleSchema = tuple({ + schema: [string(), number()], + mutable: false, + }); + + // biome-ignore lint/suspicious/noExplicitAny: unit test + const tupleChangedResult = validate(tupleSchema, originalTuple as any, changedTuple); + expect(tupleChangedResult.success).toBe(false); + + // biome-ignore lint/suspicious/noExplicitAny: unit test + const tupleSameResult = validate(tupleSchema, originalTuple as any, originalTuple); + expect(tupleSameResult.success).toBe(true); + }); + }); + describe("validation rules", () => { describe("min rule", () => { it("should pass when string length meets minimum", () => { @@ -1340,6 +1591,15 @@ describe("validate", () => { expect(validateType(SchemaType.DATE, new Date("invalid"))).toBe(false); }); + it("should validate tuple type", () => { + expect(validateType(SchemaType.TUPLE, [])).toBe(true); + expect(validateType(SchemaType.TUPLE, [1, 2, 3])).toBe(true); + expect(validateType(SchemaType.TUPLE, ["hello", "world"])).toBe(true); + expect(validateType(SchemaType.TUPLE, "not an array")).toBe(false); + expect(validateType(SchemaType.TUPLE, {})).toBe(false); + expect(validateType(SchemaType.TUPLE, null)).toBe(false); + }); + it("should validate date string type with format", () => { expect(validateType(SchemaType.DATE_STRING, "2023-12-25", "yyyy-MM-dd")).toBe(true); expect(validateType(SchemaType.DATE_STRING, "invalid-date", "yyyy-MM-dd")).toBe(false); diff --git a/packages/dynz/src/validate.ts b/packages/dynz/src/validate.ts index 4ac50bb..f14c0b7 100644 --- a/packages/dynz/src/validate.ts +++ b/packages/dynz/src/validate.ts @@ -254,7 +254,7 @@ export function _validate( /** * Validate array */ - if (schema.type === SchemaType.ARRAY) { + if (schema.type === SchemaType.ARRAY || schema.type === SchemaType.TUPLE) { if (!isArray(newValue)) { throw new Error(`new value is not an array: ${newValue}`); } @@ -271,8 +271,29 @@ export function _validate( return newValue.reduce>( (acc, cur, index) => { + const validationSchema = + schema.type === SchemaType.ARRAY ? schema.schema : (schema.schema[index] as Schema | undefined); + + if (validationSchema === undefined) { + return { + success: false, + errors: [ + { + path: `${path}.[${index}]`, + schema, + value: cur, + current: currentValue?.[index], + customCode: ErrorCode.TYPE, + code: ErrorCode.TYPE, + expectedType: schema.type, + message: `The value for schema ${path} is not of type ${schema.type}`, + }, + ], + }; + } + const result = _validate( - schema.schema, + validationSchema, { current: currentValue?.[index], new: cur, @@ -858,6 +879,8 @@ export function validateType( return isDateString(value, dateFormat); } + case SchemaType.TUPLE: + return isArray(value); case SchemaType.OPTIONS: return isNumber(value) || isString(value) || isBoolean(value); }