Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add class-validator parser #736

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"commit": false,
"fixed": [
[
"@conform-to/class-validator",
"@conform-to/dom",
"@conform-to/react",
"@conform-to/yup",
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@
"test:api": "vitest --watch",
"test:e2e": "playwright test --ui",
"lint": "eslint --cache --ext .js,.jsx,.ts,.tsx .",
"format": "prettier --write ./packages",
"prepare": "husky install"
},
"devDependencies": {
"@changesets/cli": "^2.27.5",
"@conform-to/class-validator": "workspace:*",
"@conform-to/dom": "workspace:*",
"@conform-to/react": "workspace:*",
"@conform-to/validitystate": "workspace:*",
Expand All @@ -25,6 +27,7 @@
"@types/node": "^20.10.4",
"@typescript-eslint/eslint-plugin": "^7.11.0",
"@typescript-eslint/parser": "^7.11.0",
"class-validator": "0.14.1",
"eslint": "^8.57.0",
"eslint-import-resolver-typescript": "^3.6.1",
"eslint-plugin-import": "^2.29.1",
Expand Down
7 changes: 7 additions & 0 deletions packages/conform-class-validator/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
_virtual
*.cjs
*.d.ts
*.js
*.mjs
README
!rollup.config.js
9 changes: 9 additions & 0 deletions packages/conform-class-validator/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# ignore all .ts, .tsx files except .d.ts
*.ts
*.tsx
!*.d.ts

# config / build result
rollup.config.js
tsconfig.json
*.tsbuildinfo
1 change: 1 addition & 0 deletions packages/conform-class-validator/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { parseWithClassValidator } from './parse';
62 changes: 62 additions & 0 deletions packages/conform-class-validator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
{
"name": "@conform-to/class-validator",
"description": "Conform helpers for integrating with class-validator",
"homepage": "https://conform.guide",
"license": "MIT",
"version": "0.1.0",
"main": "index.js",
"module": "index.mjs",
"types": "index.d.ts",
"exports": {
".": {
"types": "./index.d.ts",
"module": "./index.mjs",
"import": "./index.mjs",
"require": "./index.js",
"default": "./index.mjs"
}
},
"scripts": {
"build:js": "rollup -c",
"build:ts": "tsc",
"build": "pnpm run \"/^build:.*/\"",
"dev:js": "pnpm run build:js --watch",
"dev:ts": "pnpm run build:ts --watch",
"dev": "pnpm run \"/^dev:.*/\"",
"prepare": "pnpm run build"
},
"repository": {
"type": "git",
"url": "https://github.com/edmundhung/conform",
"directory": "packages/conform-class-validator"
},
"bugs": {
"url": "https://github.com/edmundhung/conform/issues"
},
"dependencies": {
"@conform-to/dom": "workspace:*"
},
"peerDependencies": {
"class-validator": "^0.14.1"
},
"devDependencies": {
"@babel/core": "^7.17.8",
"@babel/preset-env": "^7.20.2",
"@babel/preset-typescript": "^7.20.2",
"@rollup/plugin-babel": "^5.3.1",
"@rollup/plugin-node-resolve": "^13.3.0",
"rollup-plugin-copy": "^3.4.0",
"rollup": "^2.79.1",
"class-validator": "^0.14.1"
},
"keywords": [
"constraint-validation",
"form",
"form-validation",
"html",
"progressive-enhancement",
"validation",
"class-validator"
],
"sideEffects": false
}
87 changes: 87 additions & 0 deletions packages/conform-class-validator/parse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { type Submission, parse } from '@conform-to/dom';
import { type ValidationError, validate, validateSync } from 'class-validator';

class ConformClassValidatorModel<T extends Record<string, any>> {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
constructor(data: T) {
// dummy class just for typing purposes
}
}

type TConformClassValidatorModelConstructor<T extends Record<string, any>> =
new (data: T, ...args: any[]) => ConformClassValidatorModel<T>;

export function parseWithClassValidator<T extends Record<string, any>>(
payload: FormData,
config: {
schema: TConformClassValidatorModelConstructor<T>;
async?: false;
},
): Submission<ConformClassValidatorModel<T>, string[]>;

export function parseWithClassValidator<T extends Record<string, any>>(
payload: FormData,
config: {
schema: TConformClassValidatorModelConstructor<T>;
async: true;
},
): Promise<Submission<ConformClassValidatorModel<T>, string[]>>;

export function parseWithClassValidator<T extends Record<string, any>>(
payload: FormData,
config: {
schema: TConformClassValidatorModelConstructor<T>;
async?: boolean;
},
):
| Submission<ConformClassValidatorModel<T>, string[]>
| Promise<Submission<ConformClassValidatorModel<T>, string[]>> {
return parse<ConformClassValidatorModel<T>, string[]>(payload, {
resolve(payload) {
const { schema: Model } = config;

const resolveError = (errors: ValidationError[]) =>
errors.reduce(
(acc: Record<string, string[]>, current: ValidationError) => {
acc[current.property] = current.constraints
? Object.values(current.constraints)
: [];

return acc;
},
{},
);

try {
const model = new Model(payload as T);

const resolveSubmission = (errors: ValidationError[]) => {
if (errors.length > 0) {
return { value: undefined, error: resolveError(errors) };
}

return {
value: Object.getOwnPropertyNames(model).reduce(
(acc: Record<string, any>, modelPublicKey: string) => {
// @ts-ignore
acc[modelPublicKey] = model[modelPublicKey];

return acc;
},
{},
),
error: undefined,
};
};

if (!config.async) {
return resolveSubmission(validateSync(model));
}

return validate(model).then(resolveSubmission);
} catch {
throw new Error('Bad validation model passed!');
}
},
});
}
97 changes: 97 additions & 0 deletions packages/conform-class-validator/rollup.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import path from 'node:path';
import babel from '@rollup/plugin-babel';
import nodeResolve from '@rollup/plugin-node-resolve';
import copy from 'rollup-plugin-copy';

/** @returns {import("rollup").RollupOptions[]} */
function configurePackage() {
let sourceDir = '.';
let outputDir = sourceDir;

/** @type {import("rollup").RollupOptions} */
let ESM = {
external(id) {
return !id.startsWith('.') && !path.isAbsolute(id);
},
input: `${sourceDir}/index.ts`,
output: {
dir: outputDir,
format: 'esm',
preserveModules: true,
entryFileNames: '[name].mjs',
},
plugins: [
babel({
babelrc: false,
configFile: false,
presets: [
[
'@babel/preset-env',
{
targets: {
node: '16',
esmodules: true,
},
},
],
'@babel/preset-typescript',
],
plugins: [],
babelHelpers: 'bundled',
exclude: /node_modules/,
extensions: ['.ts', '.tsx'],
}),
nodeResolve({
extensions: ['.ts', '.tsx'],
}),
copy({
targets: [{ src: `../../README`, dest: sourceDir }],
}),
],
};

/** @type {import("rollup").RollupOptions} */
let CJS = {
external(id) {
return !id.startsWith('.') && !path.isAbsolute(id);
},
input: `${sourceDir}/index.ts`,
output: {
dir: outputDir,
format: 'cjs',
preserveModules: true,
exports: 'auto',
},
plugins: [
babel({
babelrc: false,
configFile: false,
presets: [
[
'@babel/preset-env',
{
targets: {
node: '16',
esmodules: true,
},
},
],
'@babel/preset-typescript',
],
plugins: [],
babelHelpers: 'bundled',
exclude: /node_modules/,
extensions: ['.ts', '.tsx'],
}),
nodeResolve({
extensions: ['.ts', '.tsx'],
}),
],
};

return [ESM, CJS];
}

export default function rollup() {
return configurePackage();
}
16 changes: 16 additions & 0 deletions packages/conform-class-validator/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"compilerOptions": {
"lib": ["ES2022", "DOM", "DOM.Iterable"],
"target": "ES2022",
"module": "ES2022",
"moduleResolution": "Bundler",
"allowSyntheticDefaultImports": true,
"noUncheckedIndexedAccess": true,
"strict": true,
"declaration": true,
"emitDeclarationOnly": true,
"composite": true,
"skipLibCheck": true,
"experimentalDecorators": true
}
}
Loading
Loading