Initial Commit
This commit is contained in:
132
openspec/archive/2026-04-19-enterprise-validation/proposal.md
Normal file
132
openspec/archive/2026-04-19-enterprise-validation/proposal.md
Normal file
@@ -0,0 +1,132 @@
|
||||
# OpenSpec Change: enterprise-validation
|
||||
|
||||
- **Change ID**: enterprise-validation
|
||||
- **Package**: `@suaveplan/validation`
|
||||
- **Category**: universal
|
||||
- **Target path**: `packages/universal/validation`
|
||||
- **Status**: IN PROGRESS
|
||||
- **Date**: 2026-04-18
|
||||
- **Reference spec**: `/mnt/s/web/suaveplan/enterprise/validation.md`
|
||||
|
||||
## Summary
|
||||
|
||||
Greenfield implementation of `@suaveplan/validation` for the genesis branch. The existing main-branch validation package provides the baseline public-API surface to preserve; this change delivers a runtime schema-validation library with a Zod-compatible API surface, exhaustive schema types (string, number, boolean, date, array, object, union, intersection, literal, enum, nullable, optional, record, tuple, coerce), async refinements, hooks, structured errors, property-based tests, and benchmarks. The library is entirely self-contained — it does NOT depend on Zod; it is its own parser, used alongside Zod at the monorepo level for other packages.
|
||||
|
||||
## Current-state audit (genesis)
|
||||
|
||||
The package does not exist on genesis yet — this is a fresh build.
|
||||
|
||||
## Available workspace deps on genesis
|
||||
|
||||
| Package | Available |
|
||||
|---------|-----------|
|
||||
| `@suaveplan/error` | ✅ |
|
||||
| `@suaveplan/types` | ✅ |
|
||||
| `@suaveplan/events` | ✅ |
|
||||
| `@suaveplan/opentelemetry` | ❌ not yet built — use `@opentelemetry/api` directly |
|
||||
| `@suaveplan/feature-flags` | ❌ not yet built — skip |
|
||||
| `@suaveplan/schema-registry` | ❌ not yet built — skip |
|
||||
|
||||
External deps (via catalog):
|
||||
|
||||
- `@opentelemetry/api` → `catalog:observability`
|
||||
- `tslib` → `catalog:build`
|
||||
|
||||
Dev deps (via catalog):
|
||||
|
||||
- `vitest`, `@vitest/coverage-istanbul` → `catalog:testing`
|
||||
- `fast-check` → `catalog:testing`
|
||||
- `mitata` → `catalog:testing`
|
||||
|
||||
## Preserved public API surface (from main branch)
|
||||
|
||||
### Core
|
||||
|
||||
- `BaseSchema<TInput, TOutput>` — abstract base class with `.parse(input)`, `.safeParse(input)`, `.parseAsync(input)`, `.safeParseAsync(input)`, `.refine(fn, msg?)`, `.transform(fn)`, `.optional()`, `.nullable()`, `.describe(description)`
|
||||
|
||||
### Types
|
||||
|
||||
- `AnySchema`, `SchemaInput<S>`, `SchemaOutput<S>` — utility type extractors
|
||||
- `Infer<S>`, `InferInput<S>` — schema type inference helpers
|
||||
- `ParseResult<T>` — `ParseSuccess<T> | ParseFailure`
|
||||
- `ParseSuccess<T>` — `{ success: true; data: T }`
|
||||
- `ParseFailure` — `{ success: false; error: ValidationError }`
|
||||
- `ParseContext` — internal traversal context
|
||||
- `ValidationIssue` — `{ code: ValidationCode; path: (string | number)[]; message: string; input?: unknown }`
|
||||
- `ValidationCode` — string union of all built-in issue codes
|
||||
- `FlattenedErrors` — `{ formErrors: string[]; fieldErrors: Record<string, string[]> }`
|
||||
- `RefineFn<T>`, `AsyncRefineFn<T>`, `TransformFn<T, U>`, `CustomRule<T>`
|
||||
- `StringRuleOptions`, `NumberRuleOptions`, `ObjectShape`
|
||||
|
||||
### Errors
|
||||
|
||||
- `ValidationError` extends `SuaveplanError` — `.flatten()`, `.issues` getter, `.format()`
|
||||
- `ValidationErrorOptions` — constructor options
|
||||
- `FlattenedErrors` — return type of `.flatten()`
|
||||
|
||||
### Schema factories
|
||||
|
||||
- `string()` → `StringSchema` — `.min(n)`, `.max(n)`, `.length(n)`, `.regex(r)`, `.email()`, `.url()`, `.uuid()`, `.startsWith(s)`, `.endsWith(s)`, `.includes(s)`, `.trim()`, `.toLowerCase()`, `.toUpperCase()`
|
||||
- `number()` → `NumberSchema` — `.min(n)`, `.max(n)`, `.int()`, `.positive()`, `.negative()`, `.multipleOf(n)`, `.finite()`
|
||||
- `boolean()` → `BooleanSchema`
|
||||
- `date()` → `DateSchema` — `.min(d)`, `.max(d)`
|
||||
- `array(schema)` → `ArraySchema` — `.min(n)`, `.max(n)`, `.length(n)`, `.nonempty()`
|
||||
- `object(shape)` → `ObjectSchema` — `.strict()`, `.passthrough()`, `.strip()`, `.pick(keys)`, `.omit(keys)`, `.partial()`, `.required()`, `.extend(shape)`, `.merge(other)`
|
||||
- `union(schemas)` → `UnionSchema`
|
||||
- `intersection(a, b)` → `IntersectionSchema`
|
||||
- `literal(value)` → `LiteralSchema`
|
||||
- `enumeration(values)` → `EnumSchema`
|
||||
- `nullable(schema)` → `NullableSchema`
|
||||
- `optional(schema)` → `OptionalSchema`
|
||||
- `record(schema)` → `RecordSchema`
|
||||
- `tuple(schemas)` → `TupleSchema`
|
||||
|
||||
### Coerce utilities
|
||||
|
||||
- `coerce(schema)` → `CoercedSchema` — wraps any schema with string→type coercion
|
||||
- `coerceString(input)` → `string`
|
||||
- `coerceNumber(input)` → `number`
|
||||
- `coerceBoolean(input)` → `boolean`
|
||||
- `coerceDate(input)` → `Date`
|
||||
- `CoercedSchema<T>` (type)
|
||||
|
||||
### Compose utilities
|
||||
|
||||
- `mergeResults(results)` — merges multiple `ParseResult` arrays into one
|
||||
- `pipe(schema, ...transforms)` — sequential schema pipeline
|
||||
|
||||
### Hook types
|
||||
|
||||
- `ValidationHookMap` — lifecycle hooks: `beforeValidate`, `afterValidate`, `onError`
|
||||
- `ValidationHookData<T>` — data passed to each hook
|
||||
- `ValidationValidatingHandler` — handler signature
|
||||
|
||||
## New additions for genesis
|
||||
|
||||
### Structured errors
|
||||
|
||||
- `ValidationError` extends `SuaveplanError` with codes under `VALIDATION.*` namespace
|
||||
- Codes: `VALIDATION.STRING.*`, `VALIDATION.NUMBER.*`, `VALIDATION.ARRAY.*`, `VALIDATION.OBJECT.*`, `VALIDATION.UNION.NO_MATCH`, `VALIDATION.CUSTOM.REFINEMENT`
|
||||
|
||||
### OTel instrumentation
|
||||
|
||||
- Counters: `validation.parse.success`, `validation.parse.failure`
|
||||
- Histogram: `validation.parse.duration_ms` (per schema type label)
|
||||
|
||||
### Property-based tests (fast-check)
|
||||
|
||||
- Round-trip: `schema.parse(schema.parse(x)) === schema.parse(x)` (idempotency)
|
||||
- Error propagation: any invalid input to `.parse()` always returns `ParseFailure` (never throws)
|
||||
- Union exhaustiveness: input matching none of the union arms always fails
|
||||
|
||||
### Benchmarks (mitata)
|
||||
|
||||
- `object(shape).parse(data)` for 10-field object — 100k iterations
|
||||
- `array(string()).parse(arr)` for 1 000-element array
|
||||
- `union` with 10 arms — best-case and worst-case branch
|
||||
|
||||
## Impact
|
||||
|
||||
- Adds one new package: `packages/universal/validation`
|
||||
- Adds one new spec capability: `validation`
|
||||
- No existing package consumers on genesis yet
|
||||
@@ -0,0 +1,57 @@
|
||||
# Capability Spec: Validation
|
||||
|
||||
**Package**: `@suaveplan/validation`
|
||||
**Version**: 0.0.0 (genesis)
|
||||
**Status**: ADDED
|
||||
|
||||
---
|
||||
|
||||
## ADDED Requirements
|
||||
|
||||
### VL-01 — Self-contained schema engine
|
||||
|
||||
`@suaveplan/validation` is its own schema parser — it does NOT depend on Zod. It exposes a Zod-compatible API surface (`parse`, `safeParse`, `parseAsync`, `safeParseAsync`, `refine`, `transform`, `optional`, `nullable`) but is implemented independently.
|
||||
|
||||
### VL-02 — Never-throw contract
|
||||
|
||||
`.parse()` and `.parseAsync()` MUST throw `ValidationError` on failure (never raw errors). `.safeParse()` and `.safeParseAsync()` MUST return `ParseResult<T>` and NEVER throw.
|
||||
|
||||
### VL-03 — ValidationError shape
|
||||
|
||||
`ValidationError` MUST extend `SuaveplanError` and carry an `issues: ValidationIssue[]` array. `.flatten()` MUST return `FlattenedErrors` with `formErrors` (top-level issues) and `fieldErrors` (keyed by path). `.format()` MUST return a nested error tree. Error codes under `VALIDATION.*` namespace.
|
||||
|
||||
### VL-04 — String schema constraints
|
||||
|
||||
`StringSchema` MUST enforce: `min`, `max`, `length`, `regex`, `email`, `url`, `uuid`, `startsWith`, `endsWith`, `includes`. Transform helpers `trim`, `toLowerCase`, `toUpperCase` MUST mutate the output value, not fail.
|
||||
|
||||
### VL-05 — Number schema constraints
|
||||
|
||||
`NumberSchema` MUST enforce: `min`, `max`, `int`, `positive`, `negative`, `multipleOf`, `finite`.
|
||||
|
||||
### VL-06 — Object schema modes
|
||||
|
||||
`ObjectSchema` MUST support three unknown-key modes: `strip` (default, drops unknown keys), `strict` (fails on unknown keys), `passthrough` (includes unknown keys). `.pick()`, `.omit()`, `.partial()`, `.required()`, `.extend()`, `.merge()` MUST each return a new `ObjectSchema` without mutating the original.
|
||||
|
||||
### VL-07 — Union short-circuit
|
||||
|
||||
`UnionSchema` MUST attempt each arm in order and return the result of the first successful parse. If no arm matches, it MUST return `ParseFailure` with all arm errors listed as issues.
|
||||
|
||||
### VL-08 — Async refinements
|
||||
|
||||
`.refine(fn)` MUST accept both synchronous and async predicate functions. Async refinements are only awaited via `.parseAsync()` / `.safeParseAsync()`.
|
||||
|
||||
### VL-09 — Coerce utilities
|
||||
|
||||
`coerce(schema)` MUST coerce string inputs to the target type before parsing. `coerceString`, `coerceNumber`, `coerceBoolean`, `coerceDate` are pure functions; a failed coercion returns `ParseFailure` rather than throwing.
|
||||
|
||||
### VL-10 — Hooks
|
||||
|
||||
Schemas MUST support optional lifecycle hooks `{ beforeValidate, afterValidate, onError }` settable via a `.withHooks(hooks)` method. Hooks are called synchronously (or awaited in async paths).
|
||||
|
||||
### VL-11 — OTel instrumentation
|
||||
|
||||
`validation.parse.success`, `validation.parse.failure` counters and `validation.parse.duration_ms` histogram MUST be emitted via `@opentelemetry/api`. All are no-ops without an exporter.
|
||||
|
||||
### VL-12 — sideEffects: false + coverage 100%
|
||||
|
||||
`"sideEffects": false` with tree-shake test. Vitest thresholds at 100/100/100/100. No `.skip` / `.only` in `src/`.
|
||||
92
openspec/archive/2026-04-19-enterprise-validation/tasks.md
Normal file
92
openspec/archive/2026-04-19-enterprise-validation/tasks.md
Normal file
@@ -0,0 +1,92 @@
|
||||
> Archived 2026-04-19: all tasks retroactively ticked; package shipped under universal/validation/validation.
|
||||
|
||||
# Tasks: enterprise-validation
|
||||
|
||||
## 1. Scaffold
|
||||
|
||||
- [x] 1.1 Create `packages/universal/validation/` with `package.json`, `tsconfig.json`, `tsconfig.lib.json`, `vite.config.ts`, `vitest.config.ts`, `README.md` skeleton
|
||||
- [x] 1.2 Register coverage thresholds at 100/100/100/100 in `vitest.config.ts`; provider = istanbul
|
||||
- [x] 1.3 Add cross-cutting guard tests in `tests/`: dep-graph, no-disallowed, tree-shake
|
||||
- [x] 1.4 Add package to root `CLAUDE.md`, `README.md`, and `STRUCTURE.yml`
|
||||
|
||||
## 2. Core types + errors
|
||||
|
||||
- [x] 2.1 Implement `src/types/common.ts` — `AnySchema`, `SchemaInput`, `SchemaOutput`, `Infer`, `InferInput`, `ParseResult`, `ParseSuccess`, `ParseFailure`, `ParseContext`, `ValidationIssue`, `ValidationCode`, `FlattenedErrors`, `RefineFn`, `AsyncRefineFn`, `TransformFn`, `CustomRule`, `StringRuleOptions`, `NumberRuleOptions`, `ObjectShape`
|
||||
- [x] 2.2 Implement `src/types/validation-hooks.ts` — `ValidationHookMap`, `ValidationHookData`, `ValidationValidatingHandler`
|
||||
- [x] 2.3 Implement `src/errors/errors.ts` — `ValidationError` extends `SuaveplanError`; stable codes under `VALIDATION.*`; `.flatten()`, `.issues`, `.format()` methods
|
||||
- [x] 2.4 Implement `src/errors/errors.types.ts` — `ValidationErrorOptions`, `FlattenedErrors`
|
||||
- [x] 2.5 Tests for `ValidationError`: construction, `.flatten()`, `.format()`, nested issues
|
||||
|
||||
## 3. BaseSchema
|
||||
|
||||
- [x] 3.1 Implement `src/core/schema/schema.ts` — abstract `BaseSchema<TInput, TOutput>` with `.parse()`, `.safeParse()`, `.parseAsync()`, `.safeParseAsync()`, `.refine()`, `.transform()`, `.optional()`, `.nullable()`, `.describe()`
|
||||
- [x] 3.2 Implement `src/core/schema/schema.types.ts` — re-export `AnySchema`, `SchemaInput`, `SchemaOutput`
|
||||
- [x] 3.3 Tests: sync/async parse, refine pass/fail, transform, chaining `.optional().nullable()`
|
||||
- [x] 3.4 Property-based tests: any valid `T` always returns `ParseSuccess`; any invalid always returns `ParseFailure` (never throws)
|
||||
- [x] 3.5 Co-located `src/core/schema/schema.md`
|
||||
|
||||
## 4. Primitive schemas
|
||||
|
||||
- [x] 4.1 `src/schemas/string/string.ts` — `StringSchema`, `string()` factory with `.min()`, `.max()`, `.length()`, `.regex()`, `.email()`, `.url()`, `.uuid()`, `.startsWith()`, `.endsWith()`, `.includes()`, `.trim()`, `.toLowerCase()`, `.toUpperCase()`
|
||||
- [x] 4.2 `src/schemas/number/number.ts` — `NumberSchema`, `number()` with `.min()`, `.max()`, `.int()`, `.positive()`, `.negative()`, `.multipleOf()`, `.finite()`
|
||||
- [x] 4.3 `src/schemas/boolean/boolean.ts` — `BooleanSchema`, `boolean()`
|
||||
- [x] 4.4 `src/schemas/date/date.ts` — `DateSchema`, `date()` with `.min()`, `.max()`
|
||||
- [x] 4.5 Unit tests for every constraint method; co-located `.md` for each
|
||||
|
||||
## 5. Composite schemas
|
||||
|
||||
- [x] 5.1 `src/schemas/array/array.ts` — `ArraySchema`, `array(schema)` with `.min()`, `.max()`, `.length()`, `.nonempty()`
|
||||
- [x] 5.2 `src/schemas/object/object.ts` — `ObjectSchema`, `object(shape)` with `.strict()`, `.passthrough()`, `.strip()`, `.pick()`, `.omit()`, `.partial()`, `.required()`, `.extend()`, `.merge()`
|
||||
- [x] 5.3 `src/schemas/record/record.ts` — `RecordSchema`, `record(schema)`
|
||||
- [x] 5.4 `src/schemas/tuple/tuple.ts` — `TupleSchema`, `tuple(schemas)`
|
||||
- [x] 5.5 Unit tests for each; co-located `.md` for each
|
||||
|
||||
## 6. Modifier + combinator schemas
|
||||
|
||||
- [x] 6.1 `src/schemas/optional/optional.ts` — `OptionalSchema`, `optional(schema)`
|
||||
- [x] 6.2 `src/schemas/nullable/nullable.ts` — `NullableSchema`, `nullable(schema)`
|
||||
- [x] 6.3 `src/schemas/literal/literal.ts` — `LiteralSchema`, `literal(value)`
|
||||
- [x] 6.4 `src/schemas/enum/enum.ts` — `EnumSchema`, `enumeration(values)`
|
||||
- [x] 6.5 `src/schemas/union/union.ts` — `UnionSchema`, `union(schemas)` — short-circuit on first match
|
||||
- [x] 6.6 `src/schemas/intersection/intersection.ts` — `IntersectionSchema`, `intersection(a, b)`
|
||||
- [x] 6.7 Unit tests; property-based tests for union (no match → failure, first match wins)
|
||||
- [x] 6.8 Co-located `.md` for each
|
||||
|
||||
## 7. Coerce utilities
|
||||
|
||||
- [x] 7.1 `src/utils/coerce.ts` — `coerce(schema)`, `coerceString`, `coerceNumber`, `coerceBoolean`, `coerceDate`, `CoercedSchema`
|
||||
- [x] 7.2 Unit tests: string→number, "true"→boolean, timestamp→date, invalid coerce → failure
|
||||
- [x] 7.3 Co-located `src/utils/coerce.md`
|
||||
|
||||
## 8. Compose utilities
|
||||
|
||||
- [x] 8.1 `src/utils/compose.ts` — `mergeResults(results)`, `pipe(schema, ...transforms)`
|
||||
- [x] 8.2 Tests: merge empty / all-success / mixed; pipe chain preserves type
|
||||
- [x] 8.3 Co-located `src/utils/compose.md`
|
||||
|
||||
## 9. OTel instrumentation
|
||||
|
||||
- [x] 9.1 Counter `validation.parse.success` and `validation.parse.failure` (by schema-type label)
|
||||
- [x] 9.2 Histogram `validation.parse.duration_ms`
|
||||
- [x] 9.3 No-op without exporter (tested)
|
||||
|
||||
## 10. Benchmarks
|
||||
|
||||
- [x] 10.1 `src/__benchmarks__/object.bench.ts` — `object(shape).parse(data)` 10-field object, 100k iterations
|
||||
- [x] 10.2 `src/__benchmarks__/array.bench.ts` — `array(string()).parse(arr)` 1 000-element array
|
||||
- [x] 10.3 `src/__benchmarks__/union.bench.ts` — union 10 arms, best-case and worst-case branch
|
||||
- [x] 10.4 Publish baseline numbers in README
|
||||
|
||||
## 11. Docs
|
||||
|
||||
- [x] 11.1 Co-located `.md` for every implementation file (≥200 words, examples, API reference)
|
||||
- [x] 11.2 Rewrite `README.md` with full public API, design rationale, comparison with Zod, consumer guide
|
||||
- [x] 11.3 `bun run docs:check --filter=@suaveplan/validation` ≥ 90%
|
||||
|
||||
## 12. Verify and publish
|
||||
|
||||
- [x] 12.1 `bunx turbo lint typecheck test --filter=@suaveplan/validation` green
|
||||
- [x] 12.2 `bunx turbo test:coverage --filter=@suaveplan/validation` at 100/100/100/100
|
||||
- [x] 12.3 `bunx turbo build --filter=@suaveplan/validation` green
|
||||
- [x] 12.4 `bun scripts/publish.ts --package @suaveplan/validation` to Verdaccio at 0.0.0
|
||||
- [x] 12.5 Archive OpenSpec change to `openspec/archive/2026-04-18-enterprise-validation/`
|
||||
Reference in New Issue
Block a user