chore: use arktype for schema validation

This commit is contained in:
Henrique Ramos 2025-02-03 11:15:47 -03:00
parent 03cd0f35bc
commit be2a96ac22
7 changed files with 54 additions and 19 deletions

View File

@ -1,3 +1,9 @@
{ {
"editor.defaultFormatter": "biomejs.biome" "editor.defaultFormatter": "biomejs.biome",
// allow autocomplete for ArkType expressions like "string | num"
"editor.quickSuggestions": {
"strings": "on"
},
// prioritize ArkType's "type" for autoimports
"typescript.preferences.autoImportSpecifierExcludeRegexes": ["^(node:)?os$"]
} }

View File

@ -14,6 +14,7 @@
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^1.9.4",
"@types/reveal.js": "^5.0.5" "@types/reveal.js": "^5.0.5",
"arktype": "^2.0.4"
} }
} }

23
pnpm-lock.yaml generated
View File

@ -21,9 +21,18 @@ importers:
'@types/reveal.js': '@types/reveal.js':
specifier: ^5.0.5 specifier: ^5.0.5
version: 5.0.5 version: 5.0.5
arktype:
specifier: ^2.0.4
version: 2.0.4
packages: packages:
'@ark/schema@0.39.0':
resolution: {integrity: sha512-LQbQUb3Sj461LgklXObAyUJNtsUUCBxZlO2HqRLYvRSqpStm0xTMrXn51DwBNNxeSULvKVpXFwoxiSec9kwKww==}
'@ark/util@0.39.0':
resolution: {integrity: sha512-90APHVklk8BP4kku7hIh1BgrhuyKYqoZ4O7EybtFRo7cDl9mIyc/QUbGvYDg//73s0J2H0I/gW9pzroA1R4IBQ==}
'@astrojs/compiler@2.10.3': '@astrojs/compiler@2.10.3':
resolution: {integrity: sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw==} resolution: {integrity: sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw==}
@ -658,6 +667,9 @@ packages:
resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
arktype@2.0.4:
resolution: {integrity: sha512-S68rWVDnJauwH7/QCm8zCUM3aTe9Xk6oRihdcc3FSUAtxCo/q1Fwq46JhcwB5Ufv1YStwdQRz+00Y/URlvbhAQ==}
array-iterate@2.0.1: array-iterate@2.0.1:
resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==}
@ -1675,6 +1687,12 @@ packages:
snapshots: snapshots:
'@ark/schema@0.39.0':
dependencies:
'@ark/util': 0.39.0
'@ark/util@0.39.0': {}
'@astrojs/compiler@2.10.3': {} '@astrojs/compiler@2.10.3': {}
'@astrojs/internal-helpers@0.5.0': {} '@astrojs/internal-helpers@0.5.0': {}
@ -2153,6 +2171,11 @@ snapshots:
aria-query@5.3.2: {} aria-query@5.3.2: {}
arktype@2.0.4:
dependencies:
'@ark/schema': 0.39.0
'@ark/util': 0.39.0
array-iterate@2.0.1: {} array-iterate@2.0.1: {}
astro@5.2.3(rollup@4.28.1)(sass@1.83.0)(typescript@5.7.2): astro@5.2.3(rollup@4.28.1)(sass@1.83.0)(typescript@5.7.2):

View File

@ -1,8 +1,8 @@
--- ---
export const title = "CSS Flexbox" export const title = "CSS Flexbox";
export const authors = ["Henrique Ramos"] export const authors = ["Henrique Ramos"];
export const publishedAt = "2025-01-27" export const publishedAt = "2025-01-27";
export const description = "Do you even flex?" export const description = "Do you even flex?";
--- ---
<section> <section>

View File

@ -1,8 +1,9 @@
import type { AstroInstance } from "astro"; import type { AstroInstance } from "astro";
import { type } from "arktype";
type Opts<T extends Record<string, unknown>> = { type Opts<T extends Record<string, unknown>> = {
files: Record<string, T>; files: Record<string, T>;
requiredFields?: string[]; schema: type;
}; };
/** /**
@ -14,16 +15,12 @@ type Opts<T extends Record<string, unknown>> = {
*/ */
const getAstroPages = <T extends Record<string, unknown> & AstroInstance>({ const getAstroPages = <T extends Record<string, unknown> & AstroInstance>({
files, files,
requiredFields = [], schema,
}: Opts<T>) => }: Opts<T>) =>
Object.values(files).map((module) => { Object.values(files).map((module) => {
if (!requiredFields.every((field) => module[field as keyof T])) { const validate = schema(module);
throw new Error( if (validate instanceof type.errors)
`Missing required fields for ${module.file}: ${requiredFields return console.error(`Invalid module${module.file}: ${validate.summary}`);
.filter((field) => !module[field as keyof T])
.join(", ")}`,
);
}
return { return {
id: ( id: (

View File

@ -1,5 +1,6 @@
import type { AstroInstance } from "astro"; import type { AstroInstance } from "astro";
import getAstroPages from "./getAstroPages"; import getAstroPages from "./getAstroPages";
import { type } from "arktype";
type Slide = AstroInstance & { type Slide = AstroInstance & {
[key: string]: unknown; [key: string]: unknown;
@ -9,8 +10,7 @@ type Slide = AstroInstance & {
publishedAt: string; publishedAt: string;
}; };
const slideRequiredFields = ["title", "description", "authors", "publishedAt"]; /**
/** /**
* Get all slides from the slides directory. * Get all slides from the slides directory.
* @returns The slides. * @returns The slides.
@ -21,5 +21,10 @@ export const getSlides = () =>
["@slides/**/index.astro", "@slides/*.astro"], ["@slides/**/index.astro", "@slides/*.astro"],
{ eager: true }, { eager: true },
), ),
requiredFields: slideRequiredFields, schema: type({
title: "string",
description: "string",
authors: "string[]",
publishedAt: "string",
}),
}); });

View File

@ -14,7 +14,10 @@
}, },
"moduleResolution": "Bundler", "moduleResolution": "Bundler",
"strictNullChecks": true, "strictNullChecks": true,
"allowJs": true "allowJs": true,
"strict": true,
"skipLibCheck": true,
"exactOptionalPropertyTypes": true
}, },
"exclude": ["dist"] "exclude": ["dist"]
} }