Skip to content

Commit 49253eb

Browse files
committed
replace vine with valibot
Signed-off-by: Kirill Mokevnin <[email protected]>
1 parent 101167a commit 49253eb

File tree

9 files changed

+100
-173
lines changed

9 files changed

+100
-173
lines changed

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
- `npm run dev`: Start Fastify with watch on http://localhost:3000.
1616
- `npm start`: Start in production mode.
1717
- `npm test` or `make test`: Run Node tests (`node --test`).
18-
- `make lint` / `make lint-fix`: Lint and autofix via ESLint.
18+
- `make lint` / `make lint-fix`: Lint and autofix.
1919
- `make check-types`: Type-check with `tsc` (JS + d.ts).
2020
- `make generate-types`: Compile TypeSpec and generate Fastify handler types.
2121
- `make migration-generate`: Generate Drizzle migrations.

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ dev:
55
npm run dev
66

77
check-types:
8-
npx tsc --noEmit
8+
npx tsc
99

1010
routes:
1111
npx fastify print-routes routes/api/users.js
@@ -14,10 +14,10 @@ migration-generate:
1414
npx drizzle-kit generate
1515

1616
lint:
17-
npx eslint .
17+
npx @biomejs/biome check
1818

1919
lint-fix:
20-
npx eslint --fix .
20+
npx @biomejs/biome check --fix
2121

2222
generate-openapi:
2323
npx tsp compile .

app.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import path from 'node:path';
22
import type { AutoloadPluginOptions } from '@fastify/autoload';
33
import AutoLoad from '@fastify/autoload';
4-
import { errors } from '@vinejs/vine';
4+
import { ValiError } from 'valibot'
55
import type { FastifyPluginAsync, FastifyServerOptions } from 'fastify';
66
import glue from 'fastify-openapi-glue';
77
import serviceHandlers from './routes/index.ts';
@@ -18,18 +18,27 @@ const app: FastifyPluginAsync<AppOptions> = async (
1818
opts,
1919
): Promise<void> => {
2020
fastify.setErrorHandler((error, _request, reply) => {
21-
if (error instanceof errors.E_VALIDATION_ERROR) {
21+
if (error instanceof ValiError) {
22+
const errors = error.issues.map((issue) => {
23+
const lastPathItem = issue.path && issue.path.length > 0 ? issue.path[issue.path.length - 1] : undefined
24+
const field = lastPathItem && 'key' in lastPathItem && lastPathItem.key != null ? String(lastPathItem.key) : ''
25+
return {
26+
message: issue.message ?? 'Validation error',
27+
rule: String(issue.type ?? 'validation'),
28+
field,
29+
}
30+
})
2231
const errorDetail = {
2332
status: 422,
2433
title: 'Validation Error',
2534
detail: 'Errors related to business logic such as uniqueness',
26-
errors: error.messages,
27-
};
28-
reply.type('application/problem+json').code(422).send(errorDetail);
35+
errors,
36+
}
37+
reply.type('application/problem+json').code(422).send(errorDetail)
2938
} else {
30-
reply.send(error);
39+
reply.send(error)
3140
}
32-
});
41+
})
3342

3443
fastify.addContentTypeParser(
3544
'application/problem+json',

package-lock.json

Lines changed: 27 additions & 90 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
"@fastify/response-validation": "^3.0.3",
1414
"@fastify/sensible": "^6.0.3",
1515
"@typespec/openapi": "^1.3.0",
16-
"@vinejs/vine": "^3.0.1",
16+
"drizzle-valibot": "^0.3.0",
17+
"valibot": "^1.0.0",
1718
"ajv-formats": "^3.0.1",
1819
"better-sqlite3": "^12.2.0",
1920
"drizzle-orm": "^0.44.4",

rules/unique.ts

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,19 @@
1-
import vine from '@vinejs/vine';
21
import { eq } from 'drizzle-orm';
3-
import { drizzle } from 'drizzle-orm/better-sqlite3';
4-
import * as schemas from '../db/schema.ts';
2+
import { checkAsync } from 'valibot';
3+
import type { DrizzleDB, DrizzleSchema } from '../types/index.ts';
54

6-
/**
7-
* @param {any} value
8-
* @param {{ schema: import('../types/index.ts').DrizzleSchema }} options
9-
* @param {import('@vinejs/vine/types').FieldContext} field
10-
*/
11-
async function unique(value, options, field) {
12-
/**
13-
* We do not want to deal with non-string
14-
* values. The "string" rule will handle the
15-
* the validation.
16-
*/
17-
if (typeof value !== 'string') {
18-
return;
19-
}
5+
type Options = {
6+
schema: DrizzleSchema;
7+
field: string;
8+
};
209

21-
/** @type {ReturnType<typeof drizzle<typeof schemas>>} */
22-
const db = field.meta.db;
23-
const [row] = await db
24-
.select()
25-
.from(options.schema)
26-
.where(eq(options.schema[field.name], value));
27-
28-
if (row) {
29-
field.report(
30-
`The {{ field }} field (= ${value}) is not unique.`,
31-
'unique',
32-
field,
33-
);
34-
}
10+
export default function unique(db: DrizzleDB, options: Options) {
11+
return checkAsync(async (value: string) => {
12+
const [row] = await db
13+
.select()
14+
.from(options.schema)
15+
// @ts-expect-error index signature for dynamic column access
16+
.where(eq(options.schema[options.field], value));
17+
return !row;
18+
});
3519
}
36-
37-
export default vine.createRule(unique, {
38-
// implicit: true,
39-
isAsync: true,
40-
});
Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
/** biome-ignore-all lint/complexity/noStaticOnlyClass: - */
22

3-
import vine from '@vinejs/vine';
4-
import type { CourseLessonInsert, DrizzleDB } from '../../types/index.ts';
3+
import type { CourseLessonInsert, DrizzleDB } from '../../types/index.ts'
4+
import { object, parseAsync, string } from 'valibot'
55

6-
const schema = vine.object({}).allowUnknownProperties();
7-
const validator = vine.compile(schema);
6+
const schema = object({
7+
name: string(),
8+
body: string(),
9+
})
810

911
class LessonValidator {
10-
static validate(db: DrizzleDB, data: CourseLessonInsert) {
11-
return validator.validate(data, { meta: { db } });
12+
static validate(_db: DrizzleDB, data: CourseLessonInsert) {
13+
return parseAsync(schema, data)
1214
}
1315
}
1416

15-
export default LessonValidator;
17+
export default LessonValidator

validators/CourseValidator.ts

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,19 @@
11
/** biome-ignore-all lint/complexity/noStaticOnlyClass: - */
22

3-
import vine from '@vinejs/vine';
4-
import type { CourseInsert, DrizzleDB } from '../types/index.ts';
3+
import type { CourseInsert, DrizzleDB } from '../types/index.ts'
4+
import { object, parseAsync, string } from 'valibot'
55

6-
const schema = vine
7-
.object({
8-
// name: vine.string(),
9-
// description: vine.string(),
10-
})
11-
.allowUnknownProperties();
12-
const validator = vine.compile(schema);
6+
// Keep validation permissive: accept fields used by routes
7+
// and let handlers add/override creatorId.
8+
const schema = object({
9+
name: string(),
10+
description: string(),
11+
})
1312

1413
class CourseValidator {
15-
static validate(db: DrizzleDB, data: CourseInsert) {
16-
return validator.validate(data, { meta: { db } });
14+
static validate(_db: DrizzleDB, data: CourseInsert) {
15+
return parseAsync(schema, data)
1716
}
1817
}
1918

20-
export default CourseValidator;
19+
export default CourseValidator

0 commit comments

Comments
 (0)