feat(objectql,spec): enforce all declared validation-rule types; trim the unenforceable three (#1475)#1485
Merged
Conversation
… the unenforceable three (#1475) The `validations` union advertised nine rule types but only three (state_machine / cross_field / script) ran on the write path — the other six were accepted by the schema yet silently no-op'd. Close the gap on both sides. objectql: the rule evaluator now also enforces `format` (regex / named email·url·phone·json), `json_schema` (ajv-compiled, memoised), and `conditional` (recursive when → then/otherwise). All three are deterministic, synchronous, side-effect-free predicates over one record. Adds ajv + three error codes; needsPriorRecord recurses into conditionals. spec: removes the `unique`, `async`, and `custom` variants — each needs I/O or a handler model a write-path rule must not carry. Redirected to the layer that already does it correctly (unique index / client form / lifecycle hook). Field-level `unique: true` is unaffected. examples/app-showcase demonstrates and verifies each newly-enforced type. Documented as an ADR-0020 addendum. Closes #1475 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
| function matchesNamedFormat(format: FormatRule['format'], str: string): boolean { | ||
| switch (format) { | ||
| case 'email': | ||
| return EMAIL_RE.test(str); |
xuyushun441-sys
added a commit
that referenced
this pull request
Jun 1, 2026
…ation (#1485 regression) (#1490) #1485 trimmed the `unique`/`async`/`custom` validation-rule types from the union, but sys_user still declared `email_unique` with type: 'unique', so ObjectSchema.create threw a ZodError at load and platform-objects.test.ts (and main CI) went red. The rule was redundant — sys_user already has a unique index on email and better-auth enforces it on the managed user table — so it's removed rather than migrated. No other object uses a trimmed type. Verified: platform-objects.test.ts 52/52 pass. Co-authored-by: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jun 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #1475. The
ObjectSchema.validationsunion declared 9 rule types but the write-path evaluator enforced only 3 (state_machine,cross_field,script); the other six were accepted by the schema yet silently did nothing — the "advertise a capability we don't deliver" anti-pattern. This closes the gap on both sides: implement the synchronous types, trim the ones that don't belong on a write path.The governing principle: a validation rule is a deterministic, synchronous, side-effect-free predicate over a single record.
Implemented enforcement (
@objectstack/objectql)format— a field value against aregexand/or named format (email/url/phone/json). Runs only when the write touches the field and the value is non-empty (requiredness stays the field validator's job); a malformed regex fails open.json_schema— a JSON field validated against a JSON Schema via ajv (compiled result memoised per schema object). Accepts a parsed object or a JSON string (unparseable string =invalid_json); an uncompilable schema fails open.conditional— evaluateswhen, recurses intothen/otherwisevia a shared dispatch. The nested rule supplies the message; the outer conditional'sseveritydecides blocking.needsPriorRecordnow recurses into conditional branches.Adds
ajvas a dependency and threeFieldValidationErrorcodes (invalid_format,invalid_json,json_schema_violation).Trimmed from the schema (
@objectstack/spec)unique,async,custom— each needs I/O or a handler model a write-path rule must not carry, so removed rather than left as silent no-ops, and redirected to the layer that already does it correctly:ObjectSchema.indexes,partialfor scope) or field-levelunique: true(a SELECT-then-INSERT rule is racy; a DB constraint isn't). Field-levelunique: trueis unaffected.debounce/validatorUrlare keystroke concepts; SSRF/latency on the server write path).beforeInsert/beforeUpdatelifecycle hooks (the existing typed extension point).Showcase (demonstrated and verified)
examples/app-showcase's Account object gains aformat(EIN regex),json_schema(support-config shape), andconditional(churn requires a reason) example, each exercised against the real evaluator intest/validation.test.ts.Docs
ADR-0020 gains an addendum recording this as the completion of its D3 ("enforce the whole
validationsunion").Verification
@objectstack/objectql— 484 pass (incl. newformat/json_schema/conditionalsuites)@objectstack/spec— 6578 pass (validation parse tests updated +json_schemacoverage added)@objectstack/example-showcase— 17 pass (incl. new write-path verification)🤖 Generated with Claude Code