Repository navigation
v0.5.0: let a provider select the operators it serves, and ship the generator - #12
Merged
Merged
Conversation
The generator narrowed the value side of a filter well and left the feature side all-or-nothing per profile. Internally it was already operator-granular — every emission site gates on ctx.enabled — but the only thing that could write to that set was the union of whole profiles, so a backend with LIKE and no POSITION could not offer $like without also advertising $contains, and a store with no notion of key absence could not decline $exists at all. Six knobs reach the set and the emitted shape, each a flag and a JS API option: --operators, --drop-operators, --no-shorthand, --max-filter-depth, --limits, and --config to hold the combination in a file checked in beside the resource schema. What a provider declines is absent from the generated schema, so a client learns it from validation rather than from a runtime error — or worse, from an empty result set. --max-filter-depth is the one that restructures the output. JSON Schema cannot count how deep an instance already is, so a bounded filter is emitted as a chain: level i offers the logical operators over level i+1 and the last offers none. Levels share the operand $defs, so the cost is n copies of a map of $refs. Unbounded stays the single self-referential object it has always been, and every default is byte-identical to before. The capability document stops echoing the request. SPEC §2.1 makes a partial profile one an implementation may not advertise, so `profiles` now lists only the profiles the final operator set covers in full, the per-field `operators` lists carry the rest, and the demotion is warned about on stderr naming the operator responsible. Declining a core operator says outright that the result is not conforming. §2.2 gains a table for the top-level members, two of which the generator already emitted and nothing described. The tool also travels with the package for the first time. tools/ was not in package.json `files` and there was no bin entry, so the generator the README points readers at could not leave the repository. It is now a bin named jql-generate — reachable from a clone or a git install, since this repository still publishes nothing to a registry, but present in `npm pack` and ready for the day it does. Two bugs surfaced on the way. --include pruned only the root filter, so with a depth cap every excluded path walked back in one $and down. And an operator whose dependency was dropped stayed: --drop-operators $regex left $flags with a dependentRequired rule naming a member additionalProperties: false forbids. The closure is read off $defs/ConstraintObject so a later dependency is handled by construction. --max-depth also stopped coercing a non-number to NaN in silence. The narrowing property is what covers the new shapes: the seeded sampler in tests/fuzz.mjs now runs over dropped operators, no-shorthand and a capped depth, because a list of filters can only re-check the leaks someone already thought of. Closes #11. Co-Authored-By: Claude <noreply@anthropic.com>
Cuts the unreleased window as 0.5.0. Minor, and not breaking: no filter valid under 0.4.0 becomes invalid. The schema's $id stays at v0.4.0, as it did across 0.3.1, because the $id tracks the grammar and the grammar did not move — query-language-schema.json is byte-identical apart from the root description already in this window. Consumers pinning that $id have nothing to do. SPEC.md's version line stays at 0.4.0 for the same reason. Co-Authored-By: Claude <noreply@anthropic.com>
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.
Closes #11.
The generator narrowed the value side of a filter well and left the feature side all-or-nothing per profile. Internally it was already operator-granular — every emission site gates on
ctx.enabled— but the only thing that could write to that set was the union of whole profiles. A backend withLIKEand noPOSITIONcould not offer$likewithout also advertising$contains; a store with no notion of key absence could not decline$existsat all.What this adds
Six knobs, each a CLI flag and a JS API option:
--operators/--drop-operators--no-shorthand--max-filter-depth <n>$and/$or/$nor/$notmay nest--limits <json|@file>--config <file>All three shapes from the issue work. Whatever a provider declines is absent from the generated schema, so a client learns it from validation rather than from a runtime error — or worse, from an empty result set.
--max-filter-depthis the one that restructures output. JSON Schema cannot count how deep an instance already is, so a bounded filter is emitted as a chain: level i offers the logical operators over level i+1 and the last offers none. Levels share the operand$defs, so the cost is n copies of a map of$refs. Unbounded stays the single self-referential object it has always been, and every default is byte-identical to before.The spec decision
Issue #11 asked whether operator-level narrowing is a tooling change or a tooling-plus-spec one. It takes the first option it lists: a partial profile is demoted.
profilesin the capability document now lists only the profiles the final operator set covers in full, the per-fieldoperatorslists carry the rest — which §2.2 already described as "a subset of those implied byprofiles" — and the demotion is warned about on stderr naming the operator responsible. Declining acoreoperator says outright that the result is not a conforming implementation.SPEC.md gains two clarifications, both about what an implementation may claim rather than what a filter means: §2.1 says out loud that accepting part of a profile is permitted and advertising it is not, and §2.2 documents the capability document's top-level members (
limitsappeared in the example and in no table;filterSchema,itemValuesandnullablewere emitted by the generator and described nowhere). No grammar change, no$idbump.Two bugs found on the way
--includepruned only the root filter, so with a depth cap every excluded path walked back in one$anddown.--drop-operators $regexleft$flagswith adependentRequiredrule naming a memberadditionalProperties: falseforbids — present in the schema and impossible to use. The closure is read off$defs/ConstraintObject, so a later dependency is handled by construction.--max-depthalso stopped coercing a non-number toNaNin silence.Verification
132 tests pass, up from 119. The new shapes are covered by the narrowing property, not a case list: the seeded sampler in
tests/fuzz.mjsnow runs over dropped operators,--no-shorthandand a capped depth, with its non-vacuity assertions intact.npm run generate:exampleruns throughexamples/pet.jql.config.jsonand the committed outputs are byte-identical; the MCP demo transcript is unchanged.Also carries
0ba799a, the #8 narrowing fix, which was pushed to its own branch and never opened as a PR. It is already documented in this changelog window.Generated with Claude Code