feat: schema-typed File<T> accessors for schema-declared sources - #23
Open
neilverc wants to merge 1 commit into
Open
feat: schema-typed File<T> accessors for schema-declared sources#23neilverc wants to merge 1 commit into
neilverc wants to merge 1 commit into
Conversation
A source can now declare a `schema` (unchanged from before), and every
declared source gets a generated `FS` accessor as always -
`fs.getSourcesDeploymentYaml()`. What's new: `File` is generic
(`File<T = string>`), and a schema-declared source's accessor returns
`File<T>` for that schema's generated interface instead of the plain-string
default. `getContent()`/`setContent()` on it deal in the parsed,
schema-valid object directly - no manual `JSON.parse`/`load`/`dump`, no
`as` cast trusted on faith.
This lives entirely in the FS/File mechanism, not in a separate hook
type: `RenderHook`, `post_render`, `dependents`, and `ValidateHook` all
still take `(ctx, fs)`, and a schema-declared source's typed accessor
works identically wherever that FS shows up - kind hooks, resource
hooks, or a dependent hook reaching into the consumer's FS.
Enforcement is two synchronous checks, not three:
1. Pre-render gate - every schema-declared source's initial content is
parsed and validated in one pass before any hook runs.
2. Validated on every access - every getContent()/setContent() call
against a schema-declared source, through the typed accessor or the
`fs.get()` escape hatch, validates against the schema at that exact
call. A bad write throws immediately, attributed to the hook that
made it. Because every access point is itself a checkpoint, there's
no bulk re-check needed after the fact.
Wire format: `sources` entries are polymorphic (bare string, or
`{path, schema?}`); compiled `Kind` carries each schema-declared
source's raw schema text under `source_schemas`. Hook entries
(`RenderHookDefinition`) are unchanged - `{path, access?}`, no
per-entry source binding.
pkg/hook gains `WithTypedSources`/`WithSchemaValidate` options, kept
decoupled from any concrete JSON Schema library - the validator is a
caller-supplied closure. pkg/build's generated `veil-types.ts` makes
`File` generic and threads each schema's interface through
`fsInterfaceNamed`.
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.
What
A source can declare a JSON Schema (
schema), same as before. What's new: every declared source's generatedFSaccessor is now typed to that schema.fs.getSourcesDeploymentYaml()still exists exactly as it always did, butFileis generic (File<T = string>) — for a schema-declared source,Tis the schema's generated interface instead of the plain-string default, sogetContent()returns the parsed, already-valid object andsetContent()takes one back, validating + re-serializing on the way out. No more hand-rolledload(file.getContent()) as Deploymentcasts trusted on faith.This lives entirely in the
FS/Filemechanism, not as a separate hook type or a per-entry binding:RenderHook,post_render,dependents, andValidateHookall still take(ctx, fs)exactly as before, and a schema-declared source's typed accessor works identically wherever thatFSshows up — kind hooks, resource hooks, or a dependent hook reaching into the consumer's FS.Wire format
sourcesentries are polymorphic: a bare string, or{path, schema?}. Every existing kind.json with a plain string array keeps working unchanged.Kindinlines each schema-declared source's raw JSON Schema text under a newsource_schemasmap (same key convention assources) — self-contained, no filesystem access needed at render time.RenderHookDefinition(hook entries underrender/post_render/validate) is unchanged —{path, access?}. There's no per-entry source binding; typing is a property of the source, surfaced through the FS accessor, not something a hook opts into.Schema enforcement — two checks
A source's
schemais a contract on the source, enforced identically regardless of which accessor a hook uses to reach it (generated typed accessor or thefs.get()escape hatch):hooks.validate), not fail-fast.getContent()/setContent()call against a schema-declared source validates against the schema at that exact call, synchronously. A bad write throws immediately, aborting the render and attributing the failure to whichever hook made it. Because every access point is itself a checkpoint, there's no separate bulk re-check needed after the fact — nothing like a "final check" stage exists.Type generation
veil buildemits one TS interface per unique schema file a kind's sources reference (named after the file, e.g.kubernetes-deployment.schema.json→KubernetesDeployment) into the kind'sveil-types.ts, and makesFilegeneric there too.fsInterfaceNamedresolves each schema-declared source's accessor toFile<T>for its schema's interface; a source with no schema keeps the plainFile(File<string>) accessor.Scope
Applies uniformly wherever an
FSis handed to a hook: kind-levelrender/post_render/validate, resource-levelmetadata.hooks.render, andhooks.dependents(a dependent hook reaching into the consumer's FS gets the consumer's typed accessors too, for free — there's no special-casing needed since typing rides with the FS/File mechanism, not the hook's own registration).Testing
pkg/hook: unit tests against the rawWithTypedSources/WithSchemaValidateoptions — typed-accessor round trip (JSON + YAML codec), rejecting an invalid write, and proving thefs.get()escape hatch is validated too (but always stays string-typed).pkg/render: end-to-end — typed-accessor round trip, invalid-write rejection (attributed to the offending hook), pre-render gate rejecting bad initial content, escape-hatch corruption caught immediately (no deferred "final check"), typed accessor + untyped hook interleaving, and a schema-less source's accessor staying a plain unvalidated string.pkg/commands: compiledkind.jsoncarriessource_schemas; generatedveil-types.tshas the schema interface plus the genericFile<T = string>accessor signature.pkg/config: source schema-file-existence validation at load time (hook-source binding validation removed along with the binding itself).SPEC.md:sources/hooksfield docs,File/FSinterface, new "Schema-typed sources" section (replaces "Typed hooks"), "Schema enforcement" (two checks, not three), dependent-hooks scope note.Manually verified end-to-end against a real project (build → typecheck → render, happy path and a deliberate schema violation) outside this repo.