Feat/shared schema package #1

Merged
NHoarau merged 3 commits from feat/shared-schema-package into main 2026-06-04 09:08:56 +00:00
7 changed files with 231 additions and 0 deletions
Showing only changes of commit d1dbf3fe77 - Show all commits

View file

@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-06-03

View file

@ -0,0 +1,47 @@
## Context
The monorepo currently has three workspace roots: `apps/api` (NestJS), `apps/web` (Nuxt), and `packages/schema` (empty placeholder). The API and web apps share no type definitions, validation logic, or database schema — each would need to duplicate these. The `packages/schema` directory exists but has no `package.json`, no `tsconfig.json`, and no source files.
The chosen stack is:
- **Drizzle ORM** (PostgreSQL) for database table definitions, relations, and migrations
- **Superstruct** for runtime schema validation (request payloads, configs, etc.)
- **TypeScript** types derived from both Drizzle and Superstruct schemas
## Goals / Non-Goals
**Goals:**
- Create a working `@workspace/schema` package with proper `package.json`, `tsconfig.json`, and build pipeline
- Define Drizzle table schemas, relations, and enums targeting PostgreSQL
- Define Superstruct validators for runtime data validation
- Export derived TypeScript types that both `api` and `web` can consume
- Wire the package into the Turborepo build graph so it builds before its consumers
**Non-Goals:**
- No migrations or database connections — this package only provides schema definitions
- No API endpoints or web UI — this is purely a data layer package
- No initial table definitions — the package structure and conventions will be set up; actual tables come in follow-up changes
## Decisions
1. **Package name `@workspace/schema`** — follows the existing `packages/` directory pattern; scoped to avoid conflicts with npm; private: true in package.json.
2. **Entry points split by concern** — three source entry points (or one barrel export):
- `src/drizzle/` — Drizzle table definitions (`pgTable`, relations, enums)
- `src/validation/` — Superstruct schemas (`object()`, `string()`, etc.)
- `src/types/` — Re-exported TypeScript types (inferred from Drizzle and Superstruct)
Single `src/index.ts` barrel re-exports everything. The `exports` field in `package.json` may expose sub-paths if the package grows large.
3. **Drizzle schema is the source of truth for DB structure** — Superstruct validators mirror the same shapes but can be more permissive (e.g., optional fields for partial updates). TypeScript types are inferred from both.
4. **`tsconfig.json` extends the root** — no root tsconfig exists yet, so `packages/schema` gets its own with `module: nodenext`, `target: ES2023`, strict mode, and declaration output.
5. **Build via `tsc`** — simple `tsc` build; no bundler needed since it's consumed internally by the monorepo.
## Risks / Trade-offs
- **[Duplication risk]** Superstruct schemas and Drizzle definitions could drift apart. Mitigation: Superstruct validators should reference Drizzle-derived types where possible, and tests should assert consistency.
- **[Build order]** Turborepo's `^build` dependsOn already handles topological order, so no config changes needed — just ensure api/web list `@workspace/schema` in dependencies.

View file

@ -0,0 +1,31 @@
## Why
The monorepo needs a single source of truth for business schema (database tables, validation rules, and shared types) that both the API and web apps can import. Currently `packages/schema` is an empty placeholder with no package.json or source files, forcing each app to duplicate type definitions and validation logic.
## What Changes
- Populate `packages/schema` with a private npm package (`@workspace/schema`) including `package.json`, `tsconfig.json`, and source entry points
- Add Drizzle ORM schema definitions for PostgreSQL (tables, relations, enums)
- Add Superstruct-based runtime validators for request/response shapes
- Export shared TypeScript types derived from both Drizzle and Superstruct definitions
- Add `packages/schema` as a workspace dependency in both `apps/api` and `apps/web`
- Update Turborepo pipeline so `schema` build runs before dependent apps
## Capabilities
### New Capabilities
- `database-schema`: Drizzle ORM table definitions, relations, and enums targeting PostgreSQL
- `validation`: Superstruct schema definitions for runtime validation of data shapes
- `shared-types`: TypeScript type exports re-exported from Drizzle and Superstruct schemas
### Modified Capabilities
None — this is the first real content in `packages/schema`.
## Impact
- `packages/schema` will get a `package.json` and build output; its contents become importable by other workspace packages
- `apps/api` and `apps/web` gain a new workspace dependency
- `turbo.json` needs no changes — `^build` already handles topological build order
- No existing code is removed or modified; this is purely additive

View file

@ -0,0 +1,42 @@
## ADDED Requirements
### Requirement: Define PostgreSQL tables with Drizzle ORM
The package SHALL export Drizzle ORM table definitions using `pgTable` from `drizzle-orm/pg-core`, with proper column types, constraints, and defaults targeting PostgreSQL.
#### Scenario: Table exports are available
- **WHEN** another package imports from `@workspace/schema`
- **THEN** all Drizzle table definitions SHALL be accessible as named exports
#### Scenario: Table uses standard column types
- **WHEN** a column is defined in a Drizzle table
- **THEN** it SHALL use a `drizzle-orm/pg-core` column type (e.g., `text()`, `integer()`, `timestamp()`, `uuid()`, `boolean()`)
### Requirement: Define PostgreSQL enums
The package SHALL support PostgreSQL enum definitions using `pgEnum` from `drizzle-orm/pg-core` for constrained string fields.
#### Scenario: Enum is defined and exportable
- **WHEN** an enum is created with `pgEnum`
- **THEN** it SHALL be exportable and usable as a column type in table definitions
### Requirement: Define table relations
The package SHALL define Drizzle relations (`relations` from `drizzle-orm`) between tables, supporting one-to-many, many-to-one, and many-to-many relationships.
#### Scenario: Relation links two tables
- **WHEN** a relation is defined between two tables
- **THEN** the related fields SHALL be queryable via Drizzle's relational query API
### Requirement: All schemas are co-located
The package SHALL colocate Drizzle table definitions, enums, and relations within the same directory structure under `src/drizzle/`.
#### Scenario: Single import for database concerns
- **WHEN** a consumer needs database schema
- **THEN** it SHALL import from `@workspace/schema` without accessing internal paths

View file

@ -0,0 +1,33 @@
## ADDED Requirements
### Requirement: Export TypeScript types inferred from Drizzle schemas
The package SHALL export TypeScript types inferred from Drizzle ORM table definitions using `InferSelectModel` and `InferInsertModel` from `drizzle-orm`, providing both row and insert shapes.
#### Scenario: Select type is available
- **WHEN** a table is defined in Drizzle
- **THEN** an `InferSelectModel` type SHALL be exported for that table
#### Scenario: Insert type is available
- **WHEN** a table is defined in Drizzle
- **THEN** an `InferInsertModel` type SHALL be exported for that table
### Requirement: Export TypeScript types inferred from Superstruct schemas
The package SHALL export TypeScript types inferred from Superstruct schemas using `Infer` from `superstruct`, so that runtime validation and compile-time types stay in sync.
#### Scenario: Infer type is available
- **WHEN** a Superstruct schema is defined
- **THEN** an `Infer` type SHALL be exported for that schema
### Requirement: Type re-exports via barrel
All TypeScript types SHALL be re-exported from a single barrel entry point (`src/index.ts`) so consumers import from `@workspace/schema` without deep path references.
#### Scenario: Single import for all types
- **WHEN** a consumer imports from `@workspace/schema`
- **THEN** all type exports SHALL be available from that single entry point

View file

@ -0,0 +1,38 @@
## ADDED Requirements
### Requirement: Define Superstruct validators for data shapes
The package SHALL export Superstruct schema definitions for validating runtime data shapes (request payloads, configuration objects, etc.).
#### Scenario: Validator validates correct data
- **WHEN** data matching the schema is passed to the validator
- **THEN** the validator SHALL return the validated data without throwing
#### Scenario: Validator rejects invalid data
- **WHEN** data not matching the schema is passed to the validator
- **THEN** the validator SHALL throw a `StructError` with details about the validation failure
### Requirement: Validator composability
Superstruct schemas SHALL be composable — schemas can reference other schemas for nested object validation.
#### Scenario: Nested object validation
- **WHEN** a schema defines a nested object field using another Superstruct schema
- **THEN** the nested schema SHALL be applied during validation of the parent
### Requirement: Optional and default fields
Superstruct schemas SHALL support optional fields (`optional()`) and fields with default values (`defaulted()`) for partial updates or configuration defaults.
#### Scenario: Optional field omitted
- **WHEN** an optional field is omitted from input data
- **THEN** the validator SHALL accept the data and not require the field
#### Scenario: Default value applied
- **WHEN** a field with a default value is omitted from input data
- **THEN** the validator SHALL apply the default value in the returned data

View file

@ -0,0 +1,38 @@
## 1. Package scaffolding
- [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
## 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
## 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
## 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
## 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