Feat/shared schema package #1
|
|
@ -24,6 +24,7 @@
|
|||
"@nestjs/common": "^11.0.1",
|
||||
"@nestjs/core": "^11.0.1",
|
||||
"@nestjs/platform-express": "^11.0.1",
|
||||
"@workspace/schema": "workspace:*",
|
||||
"reflect-metadata": "^0.2.2",
|
||||
"rxjs": "^7.8.1"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { AppController } from "./app.controller";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
describe('AppController', () => {
|
||||
describe("AppController", () => {
|
||||
let appController: AppController;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -14,9 +14,9 @@ describe('AppController', () => {
|
|||
appController = app.get<AppController>(AppController);
|
||||
});
|
||||
|
||||
describe('root', () => {
|
||||
describe("root", () => {
|
||||
it('should return "Hello World!"', () => {
|
||||
expect(appController.getHello()).toBe('Hello World!');
|
||||
expect(appController.getHello()).toBe("Hello World!");
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from './app.service';
|
||||
import { Controller, Get } from "@nestjs/common";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { Module } from "@nestjs/common";
|
||||
import { AppController } from "./app.controller";
|
||||
import { AppService } from "./app.service";
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Injectable } from "@nestjs/common";
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello World!';
|
||||
return "Hello World!";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { NestFactory } from "@nestjs/core";
|
||||
import { AppModule } from "./app.module";
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { INestApplication } from '@nestjs/common';
|
||||
import request from 'supertest';
|
||||
import { App } from 'supertest/types';
|
||||
import { AppModule } from './../src/app.module';
|
||||
import { Test, TestingModule } from "@nestjs/testing";
|
||||
import { INestApplication } from "@nestjs/common";
|
||||
import request from "supertest";
|
||||
import { App } from "supertest/types";
|
||||
import { AppModule } from "./../src/app.module";
|
||||
|
||||
describe('AppController (e2e)', () => {
|
||||
describe("AppController (e2e)", () => {
|
||||
let app: INestApplication<App>;
|
||||
|
||||
beforeEach(async () => {
|
||||
|
|
@ -16,10 +16,10 @@ describe('AppController (e2e)', () => {
|
|||
await app.init();
|
||||
});
|
||||
|
||||
it('/ (GET)', () => {
|
||||
it("/ (GET)", () => {
|
||||
return request(app.getHttpServer())
|
||||
.get('/')
|
||||
.get("/")
|
||||
.expect(200)
|
||||
.expect('Hello World!');
|
||||
.expect("Hello World!");
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
"@iconify-json/lucide": "^1.2.108",
|
||||
"@iconify-json/simple-icons": "^1.2.83",
|
||||
"@nuxt/a11y": "1.0.0-alpha.1",
|
||||
"@workspace/schema": "workspace:*",
|
||||
"@nuxt/hints": "1.1.2",
|
||||
"@nuxt/image": "2.0.0",
|
||||
"@nuxt/test-utils": "4.0.3",
|
||||
|
|
|
|||
|
|
@ -3,36 +3,36 @@
|
|||
- [x] 1.1 Create `packages/schema/package.json` with name `@workspace/schema`, private: true, and TypeScript build script
|
||||
- [x] 1.2 Create `packages/schema/tsconfig.json` extending the API's config pattern (nodenext, ES2023, strictNullChecks, declaration)
|
||||
- [x] 1.3 Add `drizzle-orm`, `superstruct`, and `@types/node` as dependencies in the new package
|
||||
- [ ] 1.4 Create `packages/schema/src/index.ts` barrel file re-exporting from sub-modules
|
||||
- [ ] 1.5 Add `@workspace/schema` as a workspace dependency in `apps/api/package.json`
|
||||
- [ ] 1.6 Add `@workspace/schema` as a workspace dependency in `apps/web/package.json`
|
||||
- [ ] 1.7 Verify `pnpm install` resolves the workspace dependency and `pnpm build` compiles the package
|
||||
- [x] 1.4 Create `packages/schema/src/index.ts` barrel file re-exporting from sub-modules
|
||||
- [x] 1.5 Add `@workspace/schema` as a workspace dependency in `apps/api/package.json`
|
||||
- [x] 1.6 Add `@workspace/schema` as a workspace dependency in `apps/web/package.json`
|
||||
- [x] 1.7 Verify `pnpm install` resolves the workspace dependency and `pnpm build` compiles the package
|
||||
|
||||
## 2. Drizzle schema definitions
|
||||
|
||||
- [ ] 2.1 Create `packages/schema/src/drizzle/` directory structure
|
||||
- [ ] 2.2 Define `pgEnum` exports for any PostgreSQL enums needed
|
||||
- [ ] 2.3 Define initial Drizzle tables with `pgTable`, proper column types, constraints, and defaults
|
||||
- [ ] 2.4 Define `relations()` between tables for relational queries
|
||||
- [ ] 2.5 Export all table definitions, enums, and relations from the Drizzle sub-module barrel
|
||||
- [x] 2.1 Create `packages/schema/src/drizzle/` directory structure
|
||||
- [x] 2.2 Define `pgEnum` exports for any PostgreSQL enums needed
|
||||
- [x] 2.3 Define initial Drizzle tables with `pgTable`, proper column types, constraints, and defaults
|
||||
- [x] 2.4 Define `relations()` between tables for relational queries
|
||||
- [x] 2.5 Export all table definitions, enums, and relations from the Drizzle sub-module barrel
|
||||
|
||||
## 3. Superstruct validation schemas
|
||||
|
||||
- [ ] 3.1 Create `packages/schema/src/validation/` directory structure
|
||||
- [ ] 3.2 Define Superstruct schemas for data shapes, with proper type constraints
|
||||
- [ ] 3.3 Support nested/optional/defaulted fields via Superstruct combinators
|
||||
- [ ] 3.4 Export all validation schemas from the validation sub-module barrel
|
||||
- [x] 3.1 Create `packages/schema/src/validation/` directory structure
|
||||
- [x] 3.2 Define Superstruct schemas for data shapes, with proper type constraints
|
||||
- [x] 3.3 Support nested/optional/defaulted fields via Superstruct combinators
|
||||
- [x] 3.4 Export all validation schemas from the validation sub-module barrel
|
||||
|
||||
## 4. Shared TypeScript types
|
||||
|
||||
- [ ] 4.1 Create `packages/schema/src/types/` directory structure
|
||||
- [ ] 4.2 Export `InferSelectModel` and `InferInsertModel` types derived from all Drizzle tables
|
||||
- [ ] 4.3 Export `Infer` types derived from all Superstruct schemas
|
||||
- [ ] 4.4 Re-export all types through the top-level `src/index.ts` barrel
|
||||
- [x] 4.1 Create `packages/schema/src/types/` directory structure
|
||||
- [x] 4.2 Export `InferSelectModel` and `InferInsertModel` types derived from all Drizzle tables
|
||||
- [x] 4.3 Export `Infer` types derived from all Superstruct schemas
|
||||
- [x] 4.4 Re-export all types through the top-level `src/index.ts` barrel
|
||||
|
||||
## 5. Wire into monorepo
|
||||
|
||||
- [ ] 5.1 Verify Turborepo topological build order — `turbo.json` `^build` should already handle `schema` → `api`/`web`
|
||||
- [ ] 5.2 Run `pnpm build` from root to confirm clean compilation
|
||||
- [ ] 5.3 Run `pnpm --filter=api test` to confirm no regressions
|
||||
- [ ] 5.4 Run `pnpm check-types` to confirm type-checking passes across all workspaces
|
||||
- [x] 5.1 Verify Turborepo topological build order — `turbo.json` `^build` should already handle `schema` → `api`/`web`
|
||||
- [x] 5.2 Run `pnpm build` from root to confirm clean compilation
|
||||
- [x] 5.3 Run `pnpm --filter=api test` to confirm no regressions
|
||||
- [x] 5.4 Run `pnpm check-types` to confirm type-checking passes across all workspaces
|
||||
|
|
|
|||
2
openspec/changes/swap-superstruct-valibot/.openspec.yaml
Normal file
2
openspec/changes/swap-superstruct-valibot/.openspec.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
schema: spec-driven
|
||||
created: 2026-06-04
|
||||
37
openspec/changes/swap-superstruct-valibot/design.md
Normal file
37
openspec/changes/swap-superstruct-valibot/design.md
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
## Context
|
||||
|
||||
The schema package currently has two manually maintained modules (`validation/` and `types/`) that mirror Drizzle table definitions. Drizzle ORM has built-in Valibot support (`drizzle-orm/valibot`) that can derive insert, update, and select schemas directly from `pgTable` definitions — eliminating the need for hand-written validators and separate type files.
|
||||
|
||||
The constant arrays in `drizzle/enums.ts` (`PROJECT_STATUSES`, `TASK_STATUSES`, `PRIORITIES`) already define the allowed values. These can feed into Valibot `picklist()` refinements via the customization callback in `createInsertSchema`/`createUpdateSchema`.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- Replace Superstruct with Valibot as the validation library
|
||||
- Derive insert/update schemas from Drizzle tables using `drizzle-orm/valibot`
|
||||
- Remove the `validation/` and `types/` source directories
|
||||
- Remove `superstruct` dependency; add `valibot`
|
||||
- Keep DB-level CHECK constraints — Valibot refinements layer on top
|
||||
- Ensure `createUpdateSchema` produces all-optional shapes (replacing manual partial logic)
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- No change to `drizzle/` module structure or table definitions
|
||||
- No change to the public import paths consumers use
|
||||
- No migration or database changes
|
||||
|
||||
## Decisions
|
||||
|
||||
1. **Use `drizzle-orm/valibot` not `drizzle-valibot` package** — the integration is built into `drizzle-orm` itself (since v1.0.0-beta.15). No separate package needed.
|
||||
|
||||
2. **One composer file for all Valibot schemas** — a single `src/valibot.ts` (or inline in `drizzle/`) containing `createInsertSchema` and `createUpdateSchema` calls for each table. No separate `validation/` directory.
|
||||
|
||||
3. **Customization via the second argument callback** — enum-constrained fields use `(schema) => pipe(schema, picklist([...ARRAY]))` to add the allowed-value constraint on top of Drizzle's auto-generated type info.
|
||||
|
||||
4. **Types are inferred, not declared** — `v.InferOutput<typeof projectInsert>` replaces the manual type aliases in `types/`. Consumers import the Valibot schema and derive types as needed.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **[New dependency]** Valibot replaces Superstruct. The API surface is similar but not identical — consumers may need to adjust error handling (Valibot uses `ValiError` not `StructError`).
|
||||
- **[Picklist sync]** The `picklist()` values reference the constant arrays, so they stay in sync automatically. No new drift risk.
|
||||
32
openspec/changes/swap-superstruct-valibot/proposal.md
Normal file
32
openspec/changes/swap-superstruct-valibot/proposal.md
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
## Why
|
||||
|
||||
The schema package currently duplicates validation logic: Superstruct schemas mirror Drizzle table definitions by hand. This is error-prone (they can drift) and requires maintaining a separate `validation/` and `types/` module. Drizzle provides first-class Valibot integration (`drizzle-orm/valibot`) that derives insert/update/select schemas directly from table definitions, eliminating duplication entirely. Valibot is also tree-shakeable, which benefits the web app's bundle.
|
||||
|
||||
## What Changes
|
||||
|
||||
- **BREAKING**: Replace Superstruct with Valibot across `@workspace/schema`
|
||||
- Remove `packages/schema/src/validation/` module (hand-written Superstruct schemas)
|
||||
- Remove `packages/schema/src/types/` module (manual type re-exports no longer needed)
|
||||
- Add Valibot-derived schemas via `createInsertSchema` and `createUpdateSchema` from `drizzle-orm/valibot`
|
||||
- Add `valibot` dependency; remove `superstruct` dependency
|
||||
- Update main barrel export in `src/index.ts`
|
||||
- Validation schemas for enum fields (`status`, `priority`) will use `picklist()` refinements derived from the existing constant arrays in `drizzle/enums.ts`
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
None — this is a modification of existing capabilities.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
- `validation`: Hand-written Superstruct schemas → auto-derived Valibot schemas from Drizzle tables
|
||||
- `shared-types`: Manually maintained Infer types → inferred automatically from Valibot schemas via `v.InferOutput`
|
||||
|
||||
## Impact
|
||||
|
||||
- `packages/schema/package.json` — replace `superstruct` with `valibot` in dependencies
|
||||
- `packages/schema/src/validation/` — entire directory removed
|
||||
- `packages/schema/src/types/` — entire directory removed
|
||||
- `packages/schema/src/index.ts` — exports change to drizzle tables + valibot schemas only
|
||||
- `apps/api` and `apps/web` import from `@workspace/schema` — no import path changes, but consumers may need to adapt to new API if they used Superstruct directly
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Export TypeScript types inferred from Drizzle schemas
|
||||
|
||||
The package SHALL export TypeScript types inferred from Drizzle ORM table definitions. These SHALL be derived from Valibot schemas via `v.InferOutput<typeof schema>` rather than manual `InferSelectModel`/`InferInsertModel` aliases.
|
||||
|
||||
#### Scenario: Select type is available via Valibot
|
||||
|
||||
- **WHEN** a table is defined in Drizzle and a `createSelectSchema` is created
|
||||
- **THEN** the row type SHALL be accessible as `v.InferOutput<typeof selectSchema>`
|
||||
|
||||
#### Scenario: Insert type is available via Valibot
|
||||
|
||||
- **WHEN** an insert schema is created via `createInsertSchema`
|
||||
- **THEN** the insert type SHALL be accessible as `v.InferOutput<typeof insertSchema>`
|
||||
|
||||
### Requirement: Export TypeScript types inferred from Valibot schemas
|
||||
|
||||
The package SHALL export TypeScript types inferred from Valibot schemas using `v.InferOutput`, so that runtime validation and compile-time types stay in sync.
|
||||
|
||||
#### Scenario: Infer type is available
|
||||
|
||||
- **WHEN** a Valibot schema is defined
|
||||
- **THEN** the corresponding `v.InferOutput` type SHALL be accessible
|
||||
|
||||
### Requirement: Type re-exports via barrel
|
||||
|
||||
All Valibot schemas SHALL be exported from the package's main entry point (`src/index.ts`) so consumers import from `@workspace/schema` without deep path references. Types are accessible via `v.InferOutput<typeof schema>` directly from the exported schema.
|
||||
|
||||
#### Scenario: Single import for all schemas
|
||||
|
||||
- **WHEN** a consumer imports from `@workspace/schema`
|
||||
- **THEN** all Drizzle tables and Valibot schemas SHALL be available from that single entry point
|
||||
- **THEN** TypeScript types SHALL NOT require a separate type-only import — consumers use `v.InferOutput<typeof importedSchema>`
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Export TypeScript types inferred from Superstruct schemas
|
||||
|
||||
**Reason**: Superstruct removed in favor of Valibot. Types are now inferred via `v.InferOutput` instead of Superstruct's `Infer`.
|
||||
**Migration**: Replace `import type { ProjectCreate } from "@workspace/schema"` with `import { projectInsert } from "@workspace/schema"; type ProjectCreate = v.InferOutput<typeof projectInsert>`
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
## MODIFIED Requirements
|
||||
|
||||
### Requirement: Define Valibot validators derived from Drizzle tables
|
||||
|
||||
The package SHALL export Valibot schemas derived from Drizzle table definitions using `createInsertSchema` and `createUpdateSchema` from `drizzle-orm/valibot`, replacing the previous hand-written Superstruct schemas.
|
||||
|
||||
#### Scenario: Insert schema available for each table
|
||||
|
||||
- **WHEN** a table is defined in Drizzle
|
||||
- **THEN** a `createInsertSchema` SHALL be exported for that table, with auto-generated defaults, nullability, and column type validation
|
||||
|
||||
#### Scenario: Update schema available for each table
|
||||
|
||||
- **WHEN** a table is defined in Drizzle
|
||||
- **THEN** a `createUpdateSchema` SHALL be exported for that table, with all non-generated fields marked optional
|
||||
|
||||
#### Scenario: Enum fields validated via picklist
|
||||
|
||||
- **WHEN** a `text()` column has a CHECK constraint restricting allowed values
|
||||
- **THEN** the corresponding Valibot schema SHALL use `picklist()` refinement derived from the matching constant array in `drizzle/enums.ts`
|
||||
|
||||
#### Scenario: Validator validates correct data
|
||||
|
||||
- **WHEN** data matching the schema is passed to `v.parse()`
|
||||
- **THEN** the validator SHALL return the validated data without throwing
|
||||
|
||||
#### Scenario: Validator rejects invalid data
|
||||
|
||||
- **WHEN** data not matching the schema is passed to `v.parse()`
|
||||
- **THEN** the validator SHALL throw a `ValiError` with details about the validation failure
|
||||
|
||||
## REMOVED Requirements
|
||||
|
||||
### Requirement: Define Superstruct validators for data shapes
|
||||
|
||||
**Reason**: Replaced by Valibot schemas auto-derived from Drizzle tables via `drizzle-orm/valibot`
|
||||
**Migration**: Replace `import { ProjectCreate } from "@workspace/schema"` with `import { projectInsert } from "@workspace/schema"` or use the Valibot equivalent
|
||||
|
||||
### Requirement: Validator composability
|
||||
|
||||
**Reason**: Redundant — Valibot schemas derived from Drizzle already handle nested types through table relations
|
||||
**Migration**: No migration needed; Valibot schemas are flat by design (one schema per table)
|
||||
|
||||
### Requirement: Optional and default fields
|
||||
|
||||
**Reason**: Handled natively by `createInsertSchema` (omits generated columns) and `createUpdateSchema` (makes all fields optional). Superstruct's `optional()` and `defaulted()` combinators no longer needed.
|
||||
**Migration**: Use `createUpdateSchema` for partial-update shapes instead of manually wrapping with `optional()`
|
||||
25
openspec/changes/swap-superstruct-valibot/tasks.md
Normal file
25
openspec/changes/swap-superstruct-valibot/tasks.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
## 1. Dependency swap
|
||||
|
||||
- [x] 1.1 Remove `superstruct` from `packages/schema/package.json` dependencies
|
||||
- [x] 1.2 Add `valibot` to `packages/schema/package.json` dependencies
|
||||
|
||||
## 2. Create Valibot schemas
|
||||
|
||||
- [x] 2.1 Create `packages/schema/src/valibot.ts` with `createInsertSchema` and `createUpdateSchema` for `projects` and `tasks`
|
||||
- [x] 2.2 Add `picklist()` refinements for enum fields (`status`, `priority`) using the constant arrays from `drizzle/enums.ts`
|
||||
- [x] 2.3 Export insert and update schemas as named exports
|
||||
|
||||
## 3. Remove old modules
|
||||
|
||||
- [x] 3.1 Delete `packages/schema/src/validation/` directory
|
||||
- [x] 3.2 Delete `packages/schema/src/types/` directory
|
||||
|
||||
## 4. Update barrel export
|
||||
|
||||
- [x] 4.1 Update `packages/schema/src/index.ts` to export Drizzle tables and Valibot schemas (remove old validation/types exports)
|
||||
|
||||
## 5. Verify
|
||||
|
||||
- [x] 5.1 Run `pnpm --filter=@workspace/schema build` to confirm compilation
|
||||
- [x] 5.2 Run `pnpm build` from root to confirm full monorepo integrity
|
||||
- [x] 5.3 Run `pnpm --filter=api test` to confirm no regressions
|
||||
28
packages/schema/package.json
Normal file
28
packages/schema/package.json
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "@workspace/schema",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"default": "./dist/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "rm -rf dist"
|
||||
},
|
||||
"dependencies": {
|
||||
"drizzle-orm": "^0.43.1",
|
||||
"drizzle-valibot": "^0.4.2",
|
||||
"valibot": "^1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^22.14.1",
|
||||
"typescript": "^5.7.3"
|
||||
}
|
||||
}
|
||||
14
packages/schema/src/drizzle/enums.ts
Normal file
14
packages/schema/src/drizzle/enums.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
export const PROJECT_STATUSES = ["active", "archived", "completed"] as const;
|
||||
|
||||
export const TASK_STATUSES = [
|
||||
"todo",
|
||||
"in_progress",
|
||||
"in_review",
|
||||
"done",
|
||||
] as const;
|
||||
|
||||
export const PRIORITIES = ["low", "medium", "high", "urgent"] as const;
|
||||
|
||||
export type ProjectStatus = (typeof PROJECT_STATUSES)[number];
|
||||
export type TaskStatus = (typeof TASK_STATUSES)[number];
|
||||
export type Priority = (typeof PRIORITIES)[number];
|
||||
4
packages/schema/src/drizzle/index.ts
Normal file
4
packages/schema/src/drizzle/index.ts
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
export { PROJECT_STATUSES, TASK_STATUSES, PRIORITIES } from "./enums";
|
||||
export { projects } from "./projects";
|
||||
export { tasks } from "./tasks";
|
||||
export { projectsRelations, tasksRelations } from "./relations";
|
||||
27
packages/schema/src/drizzle/projects.ts
Normal file
27
packages/schema/src/drizzle/projects.ts
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import { pgTable, uuid, text, timestamp, check } from "drizzle-orm/pg-core";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { PROJECT_STATUSES } from "./enums";
|
||||
|
||||
const projectStatusValues = PROJECT_STATUSES.map((s) => `'${s}'`).join(", ");
|
||||
|
||||
export const projects = pgTable(
|
||||
"projects",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").notNull().default("active"),
|
||||
createdAt: timestamp("created_at", { mode: "string" })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { mode: "string" })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
check(
|
||||
"project_status_check",
|
||||
sql`${table.status} IN (${sql.raw(projectStatusValues)})`,
|
||||
),
|
||||
],
|
||||
);
|
||||
14
packages/schema/src/drizzle/relations.ts
Normal file
14
packages/schema/src/drizzle/relations.ts
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { relations } from "drizzle-orm";
|
||||
import { projects } from "./projects";
|
||||
import { tasks } from "./tasks";
|
||||
|
||||
export const projectsRelations = relations(projects, ({ many }) => ({
|
||||
tasks: many(tasks),
|
||||
}));
|
||||
|
||||
export const tasksRelations = relations(tasks, ({ one }) => ({
|
||||
project: one(projects, {
|
||||
fields: [tasks.projectId],
|
||||
references: [projects.id],
|
||||
}),
|
||||
}));
|
||||
46
packages/schema/src/drizzle/tasks.ts
Normal file
46
packages/schema/src/drizzle/tasks.ts
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
pgTable,
|
||||
uuid,
|
||||
text,
|
||||
integer,
|
||||
timestamp,
|
||||
check,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import { sql } from "drizzle-orm";
|
||||
import { TASK_STATUSES, PRIORITIES } from "./enums";
|
||||
import { projects } from "./projects";
|
||||
|
||||
const taskStatusValues = TASK_STATUSES.map((s) => `'${s}'`).join(", ");
|
||||
const priorityValues = PRIORITIES.map((p) => `'${p}'`).join(", ");
|
||||
|
||||
export const tasks = pgTable(
|
||||
"tasks",
|
||||
{
|
||||
id: uuid("id").primaryKey().defaultRandom(),
|
||||
projectId: uuid("project_id")
|
||||
.notNull()
|
||||
.references(() => projects.id, { onDelete: "cascade" }),
|
||||
title: text("title").notNull(),
|
||||
description: text("description"),
|
||||
status: text("status").notNull().default("todo"),
|
||||
priority: text("priority").notNull().default("medium"),
|
||||
sortOrder: integer("sort_order").notNull().default(0),
|
||||
dueDate: timestamp("due_date", { mode: "string" }),
|
||||
createdAt: timestamp("created_at", { mode: "string" })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { mode: "string" })
|
||||
.notNull()
|
||||
.defaultNow(),
|
||||
},
|
||||
(table) => [
|
||||
check(
|
||||
"task_status_check",
|
||||
sql`${table.status} IN (${sql.raw(taskStatusValues)})`,
|
||||
),
|
||||
check(
|
||||
"task_priority_check",
|
||||
sql`${table.priority} IN (${sql.raw(priorityValues)})`,
|
||||
),
|
||||
],
|
||||
);
|
||||
24
packages/schema/src/index.ts
Normal file
24
packages/schema/src/index.ts
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
export {
|
||||
PROJECT_STATUSES,
|
||||
TASK_STATUSES,
|
||||
PRIORITIES,
|
||||
projects,
|
||||
tasks,
|
||||
projectsRelations,
|
||||
tasksRelations,
|
||||
} from "./drizzle/index";
|
||||
|
||||
export {
|
||||
projectSelect,
|
||||
projectInsert,
|
||||
projectUpdate,
|
||||
taskSelect,
|
||||
taskInsert,
|
||||
taskUpdate,
|
||||
} from "./valibot";
|
||||
|
||||
import type { InferOutput } from "valibot";
|
||||
import { projectSelect, taskSelect } from "./valibot";
|
||||
|
||||
export type Project = InferOutput<typeof projectSelect>;
|
||||
export type Task = InferOutput<typeof taskSelect>;
|
||||
40
packages/schema/src/valibot.ts
Normal file
40
packages/schema/src/valibot.ts
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
import {
|
||||
createInsertSchema,
|
||||
createSelectSchema,
|
||||
createUpdateSchema,
|
||||
} from "drizzle-valibot";
|
||||
import { pipe, picklist } from "valibot";
|
||||
import {
|
||||
projects,
|
||||
tasks,
|
||||
PROJECT_STATUSES,
|
||||
TASK_STATUSES,
|
||||
PRIORITIES,
|
||||
} from "./drizzle/index";
|
||||
|
||||
export const projectSelect = createSelectSchema(projects, {
|
||||
status: (schema: any) => pipe(schema, picklist([...PROJECT_STATUSES])) as any,
|
||||
});
|
||||
|
||||
export const taskSelect = createSelectSchema(tasks, {
|
||||
status: (schema: any) => pipe(schema, picklist([...TASK_STATUSES])) as any,
|
||||
priority: (schema: any) => pipe(schema, picklist([...PRIORITIES])) as any,
|
||||
});
|
||||
|
||||
export const projectInsert = createInsertSchema(projects, {
|
||||
status: (schema: any) => pipe(schema, picklist([...PROJECT_STATUSES])) as any,
|
||||
});
|
||||
|
||||
export const projectUpdate = createUpdateSchema(projects, {
|
||||
status: (schema: any) => pipe(schema, picklist([...PROJECT_STATUSES])) as any,
|
||||
});
|
||||
|
||||
export const taskInsert = createInsertSchema(tasks, {
|
||||
status: (schema: any) => pipe(schema, picklist([...TASK_STATUSES])) as any,
|
||||
priority: (schema: any) => pipe(schema, picklist([...PRIORITIES])) as any,
|
||||
});
|
||||
|
||||
export const taskUpdate = createUpdateSchema(tasks, {
|
||||
status: (schema: any) => pipe(schema, picklist([...TASK_STATUSES])) as any,
|
||||
priority: (schema: any) => pipe(schema, picklist([...PRIORITIES])) as any,
|
||||
});
|
||||
21
packages/schema/tsconfig.json
Normal file
21
packages/schema/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"isolatedModules": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
11365
pnpm-lock.yaml
11365
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue