feat(schema): add optional fallbacks and automatic named references - #230
Conversation
| "files": [ | ||
| "dist" | ||
| "dist", | ||
| "BOUNDARIES.md" |
There was a problem hiding this comment.
why did we add this? remove it from here
There was a problem hiding this comment.
Addressed in cbc2f14. Removed BOUNDARIES.md from files; the package ships dist as before. The guide remains in the repository, and published README links now point to that repository copy. Verified with npm pack --dry-run that the guide is excluded.
| const user = object({ address: schemaRef(address) }).schemaName('User'); | ||
| const root = object({ user: schemaRef(user) }); |
There was a problem hiding this comment.
If we don't apply modifiers (like .optional(), etc) there is no need to use schemaRef wrapping. We should be able to use a plain schema in this case. Ideally I would not have schemaRef at all, but I don't know if it's possible.
There was a problem hiding this comment.
Addressed in cbc2f14 using the agreed automatic-reference approach. schemaRef and ReferenceSchemaBuilder are removed completely. Use the plain constant directly, or ordinary calls such as User.optional().nullable().describe(...); these preserve its canonical definition without changing the concrete builder. Property/constraint/callback/default/fallback/extension changes discard the inherited name and canonical association, and .schemaName('NewName') establishes a new independent definition. Docs, JSDoc and regression tests cover the policy, name conflicts, JSON Schema/OpenAPI/AsyncAPI and recursion. Local gates pass (4,348 tests); GitHub checks are running.
| `InferType<S>` continues to mean validated output; | ||
| existing builder generic arguments and legacy preprocessors keep their meaning. | ||
|
|
||
| ## Optional fallbacks | ||
|
|
||
| Before, consumers needed a separate safeParse helper for each optional scalar. | ||
| Now a fallback can use the optional or nullable schema's full output type: | ||
|
|
||
| ```ts | ||
| import { boolean, object, string } from '@cleverbrush/schema'; | ||
|
|
||
| const optionalText = string().optional().catch(undefined); | ||
| const ExternalRecord = object({ | ||
| name: optionalText, | ||
| enabled: boolean().optional().catch(() => undefined) | ||
| }); | ||
| ExternalRecord.parse({ name: 42, enabled: 'unknown' }); | ||
| // { name: undefined, enabled: undefined } | ||
| ``` | ||
|
|
||
| Fallbacks are opt-in. A malformed required root object still fails. Arrays do | ||
| not silently discard invalid elements. A fallback factory runs only on failure. | ||
|
|
||
| **Compatibility limitation:** legacy optional schemas also accept `null` at | ||
| runtime, even though their inferred type does not include it. This release does | ||
| not change that behavior. Consequently, `.optional().catch(undefined)` leaves | ||
| `null` unchanged. Normalize it explicitly when the application requires this: | ||
|
|
||
| ```ts | ||
| const normalizedText = string().optional() | ||
| .addPreprocessor(value => value == null ? undefined : value) |
There was a problem hiding this comment.
I don't think we need this file at all, just update corresponding documents/readme/etc instead of creation of a new file.
There was a problem hiding this comment.
Addressed in 89abbf5. Removed BOUNDARIES.md and moved its guidance into the existing README fallback/schemaName/API-export sections. Following the agreed cleanup, also removed the new “Schemas Across Boundaries” website page and its route/navigation entries; the content now lives in the existing modifier, JSON Schema and OpenAPI reference pages. All links point to existing sections, with no replacement guide file.
There was a problem hiding this comment.
I don't think we need these tests at all, they look weird, or if we absolutely need them, let's add them to existing test files.
There was a problem hiding this comment.
Addressed in 89abbf5. Removed boundaries-docs.test.ts entirely instead of moving the generated-declaration/JSDoc inspection assertions elsewhere. JSDoc and every behavioral, type-inference and API-document regression test remain unchanged. Local validation passes: 4,347 tests in 188 files, build, lint, both site typechecks, and TypeDoc generation (zero errors). GitHub checks are running on the follow-up commit.
Original request
Implement Framework schema-composition improvements against
development, with JSDoc, documentation, unit/type tests and minor changesets. Keep optional fallback values and named definitions with local annotations; explicit input/output typing remains removed.Following review, use automatic references through ordinary modifiers instead of
schemaRef. In the latest review cleanup, remove the standalone guide and declaration-inspection test, and consolidate the new website page into existing documentation.Xpenser and the temporary consumer-experience document are not modified.
What changed
.catch(...)and preprocessor return types accept undefined/null when the schema permits them.schemaNamecreates a fresh independent definition.BOUNDARIES.md, the unmerged “Schemas Across Boundaries” website page, its route/navigation entries and incoming links. No replacement documentation file or redirect was added.boundaries-docs.test.tswithout relocating its emitted-declaration parsing assertions. JSDoc and all behavioral/type/API-document regression tests remain.cbc2f14a.Consumer example
Reasoning
Ordinary immutable builders provide the desired consumer experience without an extra wrapper. Canonical-reference metadata supports exporters without changing runtime validation.
Naming intentionally changes for shape/rule edits: apply
schemaNameafter those edits when the result needs a stable component name. Use-site modifiers retain canonical identity; independently named definitions still conflict.Legacy optional schemas continue accepting null at runtime even when inference excludes it. Existing documentation now explains why
.optional().catch(undefined)leaves null unchanged and how to normalize explicitly.Documentation belongs alongside the corresponding APIs rather than in a duplicate guide/page. The declaration-inspection test only asserted JSDoc presence, not feature behavior; it is removed while retaining JSDoc, runtime/type coverage and normal TypeDoc generation.
Blog post
Skipped: library/API review and documentation cleanup, not an application-facing feature.
Screenshots / preview evidence
No Framework PR preview environment is configured, so preview screenshots/browser QA are unavailable. Both existing documentation sites were typechecked; moved examples were exercised against built packages, and guidance placement and links were checked locally. TypeDoc HTML was generated into temporary output without modifying tracked snapshots.
Validation
npm run lintnpm run build— 21 successful package tasksnpm run test— 4,347 passed, 188 files, no type errors; one documentation-only test removednpm run typecheck:schema-sitenpm run typecheck:docs-sitenpm pack --dry-run— schema (101 files), schema-json (9), server-openapi (15); READMEs included, removed guide/builders/tests excludedgit diff --check89abbf5389abbf53Retained regression coverage
Canonical identity across all concrete builders; direct reuse and chained modifiers; explicit renaming and strict independent-name conflicts; structural/rule/extension detachment; optional fallbacks and legacy null behavior; defaults and async callbacks; nested selectors/errors and type inference; JSON Schema/OpenAPI/AsyncAPI annotations, mixed inline/named graphs and recursion.