From d1dbf3fe77de9fab612f1a2ce57d5870d3e00df8 Mon Sep 17 00:00:00 2001 From: NHoarau Date: Thu, 4 Jun 2026 10:58:36 +0400 Subject: [PATCH] docs(schema): Documentation of first change Creation of specs for openspec and opencode to start working --- .../add-shared-schema-package/.openspec.yaml | 2 + .../add-shared-schema-package/design.md | 47 +++++++++++++++++++ .../add-shared-schema-package/proposal.md | 31 ++++++++++++ .../specs/database-schema/spec.md | 42 +++++++++++++++++ .../specs/shared-types/spec.md | 33 +++++++++++++ .../specs/validation/spec.md | 38 +++++++++++++++ .../add-shared-schema-package/tasks.md | 38 +++++++++++++++ 7 files changed, 231 insertions(+) create mode 100644 openspec/changes/add-shared-schema-package/.openspec.yaml create mode 100644 openspec/changes/add-shared-schema-package/design.md create mode 100644 openspec/changes/add-shared-schema-package/proposal.md create mode 100644 openspec/changes/add-shared-schema-package/specs/database-schema/spec.md create mode 100644 openspec/changes/add-shared-schema-package/specs/shared-types/spec.md create mode 100644 openspec/changes/add-shared-schema-package/specs/validation/spec.md create mode 100644 openspec/changes/add-shared-schema-package/tasks.md diff --git a/openspec/changes/add-shared-schema-package/.openspec.yaml b/openspec/changes/add-shared-schema-package/.openspec.yaml new file mode 100644 index 0000000..0ba725f --- /dev/null +++ b/openspec/changes/add-shared-schema-package/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-06-03 diff --git a/openspec/changes/add-shared-schema-package/design.md b/openspec/changes/add-shared-schema-package/design.md new file mode 100644 index 0000000..f7017b8 --- /dev/null +++ b/openspec/changes/add-shared-schema-package/design.md @@ -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. diff --git a/openspec/changes/add-shared-schema-package/proposal.md b/openspec/changes/add-shared-schema-package/proposal.md new file mode 100644 index 0000000..6d79770 --- /dev/null +++ b/openspec/changes/add-shared-schema-package/proposal.md @@ -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 diff --git a/openspec/changes/add-shared-schema-package/specs/database-schema/spec.md b/openspec/changes/add-shared-schema-package/specs/database-schema/spec.md new file mode 100644 index 0000000..25f3da0 --- /dev/null +++ b/openspec/changes/add-shared-schema-package/specs/database-schema/spec.md @@ -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 diff --git a/openspec/changes/add-shared-schema-package/specs/shared-types/spec.md b/openspec/changes/add-shared-schema-package/specs/shared-types/spec.md new file mode 100644 index 0000000..721aa65 --- /dev/null +++ b/openspec/changes/add-shared-schema-package/specs/shared-types/spec.md @@ -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 diff --git a/openspec/changes/add-shared-schema-package/specs/validation/spec.md b/openspec/changes/add-shared-schema-package/specs/validation/spec.md new file mode 100644 index 0000000..1efebe2 --- /dev/null +++ b/openspec/changes/add-shared-schema-package/specs/validation/spec.md @@ -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 diff --git a/openspec/changes/add-shared-schema-package/tasks.md b/openspec/changes/add-shared-schema-package/tasks.md new file mode 100644 index 0000000..763cbff --- /dev/null +++ b/openspec/changes/add-shared-schema-package/tasks.md @@ -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