Skip to content
Open
36 changes: 36 additions & 0 deletions drizzle-vine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# drizzle-vine

Generate [VineJS](https://vinejs.dev) schemas from [Drizzle ORM](https://orm.drizzle.team) table definitions.

## Install

```sh
npm install drizzle-vine @vinejs/vine drizzle-orm
```

## Usage

```ts
import { createSelectSchema, createInsertSchema, createUpdateSchema } from 'drizzle-vine';
import { pgTable, serial, text, varchar } from 'drizzle-orm/pg-core';
import vine from '@vinejs/vine';

const users = pgTable('users', {
id: serial().primaryKey(),
name: text().notNull(),
email: varchar({ length: 255 }).notNull(),
});

const insertSchema = createInsertSchema(users);
const validator = vine.compile(insertSchema);

const output = await validator.validate({ name: 'Alice', email: 'alice@example.com' });
```

## Known limitations

VineJS does not have native support for `bigint` or Node.js `Buffer` values. Columns of those types fall back to `vine.any()` at both the runtime and type level. If you need validation on those columns you can supply a custom schema through the refine option.

## License

Apache-2.0
74 changes: 74 additions & 0 deletions drizzle-vine/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"name": "drizzle-vine",
"version": "0.1.0",
"description": "Generate VineJS schemas from Drizzle ORM schemas",
"type": "module",
"scripts": {
"build": "tsx scripts/build.ts",
"b": "pnpm build",
"test:types": "cd tests && tsc",
"pack": "(cd dist && npm pack --pack-destination ..) && rm -f package.tgz && mv *.tgz package.tgz",
"publish": "npm publish package.tgz",
"test": "vitest run"
},
"exports": {
".": {
"import": {
"types": "./index.d.mts",
"default": "./index.mjs"
},
"require": {
"types": "./index.d.cjs",
"default": "./index.cjs"
},
"types": "./index.d.ts",
"default": "./index.mjs"
}
},
"main": "./index.cjs",
"module": "./index.mjs",
"types": "./index.d.ts",
"publishConfig": {
"provenance": true
},
"repository": {
"type": "git",
"url": "git+https://github.com/drizzle-team/drizzle-orm.git"
},
"keywords": [
"vinejs",
"vine",
"validate",
"validation",
"schema",
"drizzle",
"orm",
"pg",
"mysql",
"postgresql",
"postgres",
"sqlite",
"database",
"sql",
"typescript",
"ts"
],
"author": "Drizzle Team",
"license": "Apache-2.0",
"peerDependencies": {
"drizzle-orm": ">=0.36.0",
"@vinejs/vine": ">=4.0.0"
},
"devDependencies": {
"@rollup/plugin-typescript": "^11.1.0",
"@types/node": "^18.15.10",
"@vinejs/vine": "4.3.0",
"cpy": "^10.1.0",
"drizzle-orm": "link:../drizzle-orm/dist",
"rimraf": "^5.0.0",
"rollup": "^3.29.5",
"vite-tsconfig-paths": "^4.3.2",
"vitest": "^3.1.3",
"zx": "^7.2.2"
}
}
33 changes: 33 additions & 0 deletions drizzle-vine/rollup.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import typescript from '@rollup/plugin-typescript';
import { defineConfig } from 'rollup';

export default defineConfig([
{
input: 'src/index.ts',
output: [
{
format: 'esm',
dir: 'dist',
entryFileNames: '[name].mjs',
chunkFileNames: '[name]-[hash].mjs',
sourcemap: true,
},
{
format: 'cjs',
dir: 'dist',
entryFileNames: '[name].cjs',
chunkFileNames: '[name]-[hash].cjs',
sourcemap: true,
},
],
external: [
/^drizzle-orm\/?/,
'@vinejs/vine',
],
plugins: [
typescript({
tsconfig: 'tsconfig.build.json',
}),
],
},
]);
16 changes: 16 additions & 0 deletions drizzle-vine/scripts/build.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#!/usr/bin/env -S pnpm tsx
import 'zx/globals';
import cpy from 'cpy';

await fs.remove('dist');
await $`rollup --config rollup.config.ts --configPlugin typescript`;
await $`resolve-tspaths`;
await fs.copy('README.md', 'dist/README.md');
await cpy('dist/**/*.d.ts', 'dist', {
rename: (basename) => basename.replace(/\.d\.ts$/, '.d.mts'),
});
await cpy('dist/**/*.d.ts', 'dist', {
rename: (basename) => basename.replace(/\.d\.ts$/, '.d.cts'),
});
await fs.copy('package.json', 'dist/package.json');
await $`scripts/fix-imports.ts`;
136 changes: 136 additions & 0 deletions drizzle-vine/scripts/fix-imports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
#!/usr/bin/env -S pnpm tsx
import 'zx/globals';

import path from 'node:path';
import { parse, print, visit } from 'recast';
import parser from 'recast/parsers/typescript';

function resolvePathAlias(importPath: string, file: string) {
if (importPath.startsWith('~/')) {
const relativePath = path.relative(path.dirname(file), path.resolve('dist.new', importPath.slice(2)));
importPath = relativePath.startsWith('.') ? relativePath : './' + relativePath;
}

return importPath;
}

function fixImportPath(importPath: string, file: string, ext: string) {
importPath = resolvePathAlias(importPath, file);

if (!/\..*\.(js|ts)$/.test(importPath)) {
return importPath;
}

return importPath.replace(/\.(js|ts)$/, ext);
}

const cjsFiles = await glob('dist/**/*.{cjs,d.cts}');

await Promise.all(cjsFiles.map(async (file) => {
const code = parse(await fs.readFile(file, 'utf8'), { parser });

visit(code, {
visitImportDeclaration(path) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs');
this.traverse(path);
},
visitExportAllDeclaration(path) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs');
this.traverse(path);
},
visitExportNamedDeclaration(path) {
if (path.value.source) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.cjs');
}
this.traverse(path);
},
visitCallExpression(path) {
if (path.value.callee.type === 'Identifier' && path.value.callee.name === 'require') {
path.value.arguments[0].value = fixImportPath(path.value.arguments[0].value, file, '.cjs');
}
this.traverse(path);
},
visitTSImportType(path) {
path.value.argument.value = resolvePathAlias(path.value.argument.value, file);
this.traverse(path);
},
visitAwaitExpression(path) {
if (print(path.value).code.startsWith(`await import("./`)) {
path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.cjs');
}
this.traverse(path);
},
});

await fs.writeFile(file, print(code).code);
}));

let esmFiles = await glob('dist/**/*.{js,d.ts}');

await Promise.all(esmFiles.map(async (file) => {
const code = parse(await fs.readFile(file, 'utf8'), { parser });

visit(code, {
visitImportDeclaration(path) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.js');
this.traverse(path);
},
visitExportAllDeclaration(path) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.js');
this.traverse(path);
},
visitExportNamedDeclaration(path) {
if (path.value.source) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.js');
}
this.traverse(path);
},
visitTSImportType(path) {
path.value.argument.value = fixImportPath(path.value.argument.value, file, '.js');
this.traverse(path);
},
visitAwaitExpression(path) {
if (print(path.value).code.startsWith(`await import("./`)) {
path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.js');
}
this.traverse(path);
},
});

await fs.writeFile(file, print(code).code);
}));

esmFiles = await glob('dist/**/*.{mjs,d.mts}');

await Promise.all(esmFiles.map(async (file) => {
const code = parse(await fs.readFile(file, 'utf8'), { parser });

visit(code, {
visitImportDeclaration(path) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs');
this.traverse(path);
},
visitExportAllDeclaration(path) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs');
this.traverse(path);
},
visitExportNamedDeclaration(path) {
if (path.value.source) {
path.value.source.value = fixImportPath(path.value.source.value, file, '.mjs');
}
this.traverse(path);
},
visitTSImportType(path) {
path.value.argument.value = fixImportPath(path.value.argument.value, file, '.mjs');
this.traverse(path);
},
visitAwaitExpression(path) {
if (print(path.value).code.startsWith(`await import("./`)) {
path.value.argument.arguments[0].value = fixImportPath(path.value.argument.arguments[0].value, file, '.mjs');
}
this.traverse(path);
},
});

await fs.writeFile(file, print(code).code);
}));
Loading