Skip to content

Commit 63dc4f7

Browse files
os-teslaclaude
andauthored
refactor(data-objectstack): rename the spec-named filter-operator fold (objectui#7265 data-objectstack slice) (#9527)
Part of objectui#7265 — the `@object-ui/data-objectstack` slice of the `check:spec-symbols` DEBT burn-down. One group remains (`@object-ui/plugin-detail`), so this carries no closing keyword by design. ## What moved `packages/data-objectstack/src/index.ts` declared a module-local `normalizeFilterOperator` — the exact name `@objectstack/spec/ui` exports, and which this monorepo's view layer imports from there (`viewFilterFold`, `filter-converter`, `ListView`, `UserFilters`, the FilterBuilder). It is now `toAstFilterOperator`; rule 1's `DEBT` block is regenerated mechanically with `--ledger`. ## Route: RENAME — and the measurement that refused BIND Re-measured against the **RESOLVED pin, `@objectstack/spec@17.4.0`** (declared range `^17.2.0`; "same behaviour" is a statement about a version exactly as "byte-identical" is). The two functions take the same input and land in different vocabularies: | input | spec's `normalizeFilterOperator` | this package's fold | | --- | --- | --- | | `eq` | `equals` | the equals symbol | | `equals` | `equals` | the equals symbol | | `greater_than` | `greater_than` | the greater-than symbol | | `before` | `before` | the less-than symbol | The spec's folds an authored spelling to the canonical VIEW vocabulary so `ViewFilterRuleSchema`'s enum can judge it (it is the `z.preprocess` step on `ViewFilterRuleSchema.operator`). This package's translates the same input into the server's filter-AST SYMBOLS. Same input, different codomain — so binding would have changed what goes on the wire for most of the operators a stored view can carry. A function also has no derive-in-place form: rule 1 records every function declaration with `derived: false`, which leaves exactly three exits (delete and import, rename, waive). RENAME removes the collision instead of excusing it, and the declaration was never exported, so renaming costs nothing outside the file. ### What the measurement overturned — the `?? op` tail The dispatch's hypothesis was that the spec's version REFUSES where this one is lenient. Measured, it is the other way round, in both halves: - **The lenient tail is COMMON ground, not the divergence.** Both hand an unrecognised STRING back unchanged: `normalizeFilterOperator('totally_unknown')` returns `'totally_unknown'`, and so does this package's table lookup. "The spec refuses instead" is not a difference that exists. - **The tail DOES diverge — on the non-string arm, and the spec is the lenient one.** The spec's body ends `return op as string`, so a non-string comes straight back (`42` returns `42`, `null` returns `null`). This package returns `null` there, and `objectFilterEntryToAST` reads that `null` one line later into a `MalformedFilterError`. **Binding the spec would therefore have WIDENED what this adapter accepts onto the wire** — a number in the operator slot of a tuple it is about to send, instead of a 400-shaped refusal. That refusal is already pinned end-to-end, through the real adapter and both `find()` routes, by the `operator: 42` case in `packages/data-objectstack/src/filter-entry-translation.test.ts`. **Clause-② stays `no`:** nothing this PR does changes what the package sends or what it accepts as an operator — the `?? op` tail, the `null` arm and every row of `FILTER_OPERATOR_ALIASES` are byte-for-byte unchanged. The trip-wire was checked, not assumed. ## The INTERMEDIATE reading (site edit, not a ledger edit) The gate was run after the site edit and **before** regenerating the block. Real output: ``` ❌ a spec-named symbol is hand-written, not derived: • @object-ui/data-objectstack lists 1 symbol in DEBT that no longer collide — `normalizeFilterOperator`. Delete them from scripts/check-spec-symbol-derivation.mjs (`--ledger` regenerates the block) so the names cannot be re-forked silently […] ``` The gate's trailing clause is elided on purpose: it spells a closing keyword immediately before this card's number, and GitHub's linked-issue parser does not read sentences or spare fenced blocks — quoting it verbatim here risks closing a card that still has a group left. Read the full wording in the gate's source (its stale-entry message). Seat attribution for this run, as prose so it survives an edit: `session_011QreXiyMEqKLN4U5daMPVa`. `exit 1`. That message names the entry as **stale** — which is only reachable if the declaration left the tree first. ## Ledger, before and after Banner before (at `bbc9dc3`): `18 declared dialects, 2 untriaged collisions in 2 packages.` — `exit 0` Banner after: `18 declared dialects, 1 untriaged collision in 1 packages.` — `exit 0` `--ledger` re-run after the splice is byte-identical to what was spliced in (idempotent). `--claim-ledger` regenerates `CLAIM_DEBT` byte-identically across this change — the declaration that moved carried no spec-alignment claim, so rule 2's block is untouched. ## The stale figure, deleted rather than re-derived The `DEBT_ISSUE` note carried a figure written by an earlier slice and overtaken by every slice since: it said how many entries objectui#7265 had burned out of each block. Per AGENTS.md #9 it is **deleted, not re-derived to a new number**. The sentence now names the routes (BIND / RENAME / ALLOW) and the instruments (`--ledger`, `--claim-ledger`, and the run banner) and carries no count at all. One extra deletion, declared: the same note's preceding clause read `its 18-entry block is live`, and the sentence I was asked to rewrite ended `only the COUNT above moved` — pointing straight at it. Leaving the adjective while writing "no count is written here" would have made the replacement contradict the line above it, so `18-entry` went too. It is `its block is still live` now; the argument (the anchor is closed while the block is not empty) is intact. ## Pins that FAIL before and pass after Both legs were run **from the committed state**, mutation proved on disk by marker counts and blob hashes, restored with `git checkout HEAD -- PATH` and confirmed by an empty `git diff HEAD`. Neither leaves a file behind. **Leg 1 — the site.** Restored `packages/data-objectstack/src/index.ts` from the pinned base commit (marker counts moved `toAstFilterOperator` 2 to 0, `function normalizeFilterOperator` 0 to 1; on-disk hash changed): ``` GATE EXIT=1 • @object-ui/data-objectstack declares 1 spec-named symbol the spec already owns: function `normalizeFilterOperator` (exported by `@objectstack/spec/ui`) PIN EXIT=1 Tests 1 failed | 14 passed (15) × rule 1 no longer sees `normalizeFilterOperator` — the declaration is `toAstFilterOperator` now ``` Exactly one assertion fails, and it is the site assertion. Green on the branch: `15 passed (15)`. **Leg 2 — the figure.** Put the deleted figure back: ``` PIN EXIT=1 × carries no entry count at all AssertionError: the `DEBT_ISSUE` note has a ledger size written into it again. … expected [ '18-entr', 'two entr' ] to deeply equal [] ``` It catches BOTH re-introduced counts, which is what makes it a ratchet against a helpful re-derivation rather than a spelling pin. ## Ratchets left behind - **The name**, in this package's own spec-symbol file `packages/data-objectstack/src/spec-symbol-batch6.test.ts` — appended to its existing `RENAMES` table rather than duplicated into a new file. Both directions: the spec must not come to own `toAstFilterOperator`, and it must still own `normalizeFilterOperator` (if it stops, the rename's reason is spent and the plain name can be taken back). - **The site, the block and the figure**, in `scripts/__tests__/spec-symbol-ledger-data-objectstack-7265.test.ts` — the sibling shape of the core / app-shell / types / components pins. Its controls include one this slice had to add: a fixture that imports the spec's function and re-declares a wrapper under the same name is STILL flagged, which is the assertion that records why a function's route set is smaller than a type's. - **The behaviour**, in `packages/data-objectstack/src/filter-operator-ast-parity.test.ts`, beside the table it is about. The divergence set is DERIVED from the spec's own vocabularies and enumerated, never counted, with a lit control (`contains` agrees on both sides) so "they differ" cannot be vacuous. ## Verification - `pnpm exec vitest run packages/data-objectstack/ packages/plugin-view/ scripts/__tests__/` — `Test Files 265 passed | 2 skipped (267)`, `Tests 5910 passed | 2 skipped (5912)`. - `pnpm --filter @object-ui/data-objectstack --filter @object-ui/plugin-view run type-check` — exit 0, both scripts echoed. This package's `tsconfig.json` compiles its test tree (confirmed with `--listFiles`: both edited test files are in the program), so the pins are checked by `tsc --noEmit` too. - `pnpm run type-check:scripts` — exit 0 (covers the new `scripts/__tests__` pin). - Gates: `check:spec-symbols` exit 0, `check:changeset-presence` exit 0, `changeset:check` exit 0, `check:new-line-citations` `0 new citation(s)`, `check:control-bytes` OK, `check:test-path-roots` OK. - `check-governed-queue-guard --test` over every changed path: `NOT GOVERNED — none matched`. Ordinary review route. - **Lint, narrowed and measured.** `eslint --no-inline-config --format json` over the six changed files reports `6` files linted, `4 errors / 130 warnings`, all in `packages/data-objectstack/src/index.ts`. The same command over that file at the base commit reports the **same** `4 errors / 130 warnings`, same four `no-console` rules, shifted by exactly the 30 lines this PR adds: zero new findings. Type-aware linting is not configured in `eslint.config.js` (no `parserOptions.project` / `projectService`), so this diff cannot move the verdict of any file it does not contain. The repo-wide `pnpm lint` is CI's run, not this one. ## Acceptance notes `check:changeset-claims` (report-only) flagged four pending changesets whose bodies name `scripts/check-spec-symbol-derivation.mjs`. Read, as the gate asks. **This PR falsifies none of them** — three describe derivation forms and rule 2's comment attachment, all still accurate. The fourth, `.changeset/6286-explain-batch-cap-from-spec.md`, says "both of its scanners skip non-exported declarations"; that sentence is already false, and was made so by objectui#6291 widening rule 1, not by this branch. It is another card's pending body and it publishes verbatim into the CHANGELOG — reported rather than edited here. Noted, not filed: - `scripts/__tests__/check-spec-symbol-derivation.test.ts` carries a comment saying `isContextToken` and `normalizeFilterOperator` "are the live instances … both DEBT entries today". The first half went false with the `@object-ui/core` slice; this PR takes the second. Not repaired here on the same reasoning the components slice declined the figure — another slice's paragraph, not this slice's file moment. Taker: the `@object-ui/plugin-detail` slice, which empties the block and inherits the whole sentence. ## Departure from the dispatch, declared The dispatch's file surface named the site, the gate script and the parity ratchet. One file outside it is in this diff: `packages/plugin-view/src/config/__tests__/view-operator-builder-parity.test.ts`, whose header cited `data-objectstack`'s `normalizeFilterOperator` by symbol name. A rename that leaves dangling citations is an incomplete rename, and AGENTS.md #11's whole thesis is that the person who moves the thing never sees the citation — this seat did see it. One comment, no behaviour, and `plugin-view` is in the verification set above because of it. Do not enqueue; draft by instruction, and auto-merge is deliberately not enabled. --- _Generated by [Claude Code](https://claude.ai/code/session_011QreXiyMEqKLN4U5daMPVa)_ --- _Generated by [Claude Code](https://claude.ai/code)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent bbc9dc3 commit 63dc4f7

7 files changed

Lines changed: 514 additions & 15 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
---
3+
4+
Internal only, no release: `@object-ui/data-objectstack` stops declaring a
5+
module-local `normalizeFilterOperator`, the exact name `@objectstack/spec/ui`
6+
exports, and calls it `toAstFilterOperator` instead (objectui#7265, the
7+
`@object-ui/data-objectstack` slice of the DEBT ledger in
8+
`scripts/check-spec-symbol-derivation.mjs`).
9+
10+
The route is RENAME rather than BIND, and it was decided by measurement against
11+
the RESOLVED 17.4.0 pin rather than by the name. The spec's function folds an
12+
authored spelling to the canonical VIEW vocabulary so `ViewFilterRuleSchema`'s
13+
enum can judge it; this package's translates the same input into the server's
14+
filter-AST symbols, so the spec answers `equals` and `before` where this one
15+
answers `=` and `<`. Same input, different codomain: swapping it in would have
16+
changed what goes on the wire for most of the operators a stored view can carry.
17+
18+
Two things the measurement overturned, both now pinned. The `?? op` tail is not
19+
where the two differ — both hand an unrecognised string back unchanged, so the
20+
lenient tail is common ground. The tail does diverge on the other arm, and in the
21+
opposite direction: the spec returns a NON-string verbatim where this package
22+
returns `null`, and `objectFilterEntryToAST` reads that `null` into a
23+
`MalformedFilterError`. Binding the spec would therefore have widened what this
24+
adapter accepts onto the wire, not tightened it.
25+
26+
The declaration was never exported and no behaviour moved with the name, so
27+
nothing published changes and no package is released by this change.

‎packages/data-objectstack/src/filter-operator-ast-parity.test.ts‎

Lines changed: 70 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,17 @@
1010
* Adapter operator table → filter-AST parity (#2901, objectstack#3948, #3641).
1111
*
1212
* `FILTER_OPERATOR_ALIASES` is the last translation a filter passes through
13-
* before it goes on the wire, and `normalizeFilterOperator` ends in `?? op` —
13+
* before it goes on the wire, and `toAstFilterOperator` ends in `?? op` —
1414
* an unmapped operator is emitted verbatim. The server then rejects the shape
1515
* at `isFilterAST()`, passes the array through unconverted, and driver-sql
1616
* skips it entirely: **no WHERE clause, no error, every row returned.**
1717
*
18+
* (`toAstFilterOperator` was spelled `normalizeFilterOperator` until
19+
* objectui#7265 renamed it: `@objectstack/spec/ui` exports a DIFFERENT function
20+
* under that name, one that folds to the canonical VIEW vocabulary rather than
21+
* to the AST symbols this table produces. The rename is pinned in
22+
* `scripts/__tests__/spec-symbol-ledger-data-objectstack-7265.test.ts`.)
23+
*
1824
* So a missing row in this table is not a validation failure, it is an
1925
* unfiltered query. `before`/`after` — canonical members of the spec's
2026
* `VIEW_FILTER_OPERATORS` — were missing, which is exactly how a stored
@@ -48,7 +54,7 @@
4854
*/
4955
import { describe, it, expect } from 'vitest';
5056
import { VALID_AST_OPERATORS } from '@objectstack/spec/data';
51-
import { VIEW_FILTER_OPERATORS } from '@objectstack/spec/ui';
57+
import { VIEW_FILTER_OPERATORS, normalizeFilterOperator } from '@objectstack/spec/ui';
5258
import { FILTER_OPERATOR_ALIASES } from './index';
5359

5460
/**
@@ -106,7 +112,7 @@ describe('FILTER_OPERATOR_ALIASES lands inside the spec AST vocabulary', () => {
106112
});
107113

108114
it('has a mapping row for every canonical view operator the spec defines', () => {
109-
// Resolution mirrors `normalizeFilterOperator` — lowercased spelling first,
115+
// Resolution mirrors `toAstFilterOperator` — lowercased spelling first,
110116
// then the operator as written — but stops short of its `?? op` tail. That
111117
// tail is production behaviour and must stay there; reproducing it HERE is
112118
// what cancelled this assertion (#3641), because the value it falls back to
@@ -133,3 +139,64 @@ describe('FILTER_OPERATOR_ALIASES lands inside the spec AST vocabulary', () => {
133139
expect(FILTER_OPERATOR_ALIASES.after).toBe('>');
134140
});
135141
});
142+
143+
/**
144+
* This table is NOT the spec's `normalizeFilterOperator` (objectui#7265).
145+
*
146+
* The local fold over this table was called `normalizeFilterOperator` until that
147+
* card renamed it `toAstFilterOperator`, because `@objectstack/spec/ui` exports a
148+
* function of that name and most of this monorepo imports it. The two take the
149+
* same input and land in different vocabularies, which is the whole reason the
150+
* route was RENAME and not BIND — so the difference is measured here, against the
151+
* resolved pin, rather than asserted in a comment. The spec-side half of the same
152+
* measurement (its fold, its `?? op` tail, its lenient non-string arm) is in
153+
* `scripts/__tests__/spec-symbol-ledger-data-objectstack-7265.test.ts`.
154+
*/
155+
describe('this table is a different vocabulary from the spec fold that shares its old name', () => {
156+
/** Resolution as `toAstFilterOperator` does it, minus its `?? op` tail. */
157+
const row = (op: string) => FILTER_OPERATOR_ALIASES[op.toLowerCase()] ?? FILTER_OPERATOR_ALIASES[op];
158+
159+
it('answers an AST symbol where the spec answers a canonical view word', () => {
160+
const viewVocabulary = new Set<string>(VIEW_FILTER_OPERATORS);
161+
162+
// Both spellings of one operator: the spec folds them together onto a view
163+
// word, this table translates them together onto a wire symbol.
164+
for (const spelling of ['eq', 'equals']) {
165+
expect(String(normalizeFilterOperator(spelling))).toBe('equals');
166+
expect(row(spelling)).toBe('=');
167+
}
168+
// …and the two answers are in different vocabularies, not two spellings of
169+
// one. Read off the spec's own list rather than restated.
170+
expect(viewVocabulary.has('equals')).toBe(true);
171+
expect(viewVocabulary.has('=')).toBe(false);
172+
});
173+
174+
it('…and it is most of the vocabulary, not one operator — enumerated, never counted', () => {
175+
// Every canonical view operator this adapter is the bridge for, plus the
176+
// legacy spellings this table carries rows for. The set is DERIVED so it
177+
// cannot go stale, and the assertion is a floor on which members diverge
178+
// rather than a number, per AGENTS.md #9.
179+
const corpus = [
180+
...VIEW_FILTER_OPERATORS.filter((op) => !NOT_THIS_ADAPTERS_JOB.has(op)),
181+
...Object.keys(FILTER_OPERATOR_ALIASES),
182+
];
183+
const agree: string[] = [];
184+
const diverge: string[] = [];
185+
for (const op of new Set(corpus)) {
186+
(String(normalizeFilterOperator(op)) === String(row(op)) ? agree : diverge).push(op);
187+
}
188+
189+
// The divergence is the finding…
190+
expect(diverge).toContain('eq');
191+
expect(diverge).toContain('greater_than');
192+
expect(diverge).toContain('before');
193+
expect(diverge.length).toBeGreaterThan(agree.length);
194+
195+
// …and the agreement is the lit control that keeps it from being vacuous: a
196+
// comparison where NOTHING matched would mean the probe is broken, not that
197+
// the functions differ. `contains` is an identity row on both sides.
198+
expect(agree).toContain('contains');
199+
expect(String(normalizeFilterOperator('contains'))).toBe('contains');
200+
expect(row('contains')).toBe('contains');
201+
});
202+
});

‎packages/data-objectstack/src/index.ts‎

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,37 @@ export const FILTER_OPERATOR_ALIASES: Record<string, string> = {
146146
after: '>',
147147
};
148148

149-
function normalizeFilterOperator(op: unknown): string | null {
149+
/**
150+
* Resolve an authored filter operator to the AST symbol the wire takes.
151+
*
152+
* Deliberately NOT named `normalizeFilterOperator`. That name belongs to a
153+
* DIFFERENT function, which `@objectstack/spec/ui` exports and which this
154+
* monorepo's view layer imports from there (`viewFilterFold`,
155+
* `filter-converter`, `ListView`, `UserFilters`, the FilterBuilder). The two
156+
* take the same input and agree on nothing else: the spec's folds a legacy
157+
* spelling to the canonical VIEW vocabulary (`VIEW_FILTER_OPERATORS`) so that
158+
* `ViewFilterRuleSchema`'s enum can judge it, leaving a canonical operator
159+
* unchanged — `eq` becomes `equals` and `before` stays `before`. This one
160+
* translates the same input into the server's filter-AST symbols through
161+
* {@link FILTER_OPERATOR_ALIASES} — `eq` AND `equals` both become `=`, and
162+
* `before` becomes `<`. Which spellings the two disagree on is enumerated by
163+
* the pin named below rather than written down here; it is most of them.
164+
*
165+
* Renamed at objectui#7265, off the ledger in
166+
* `scripts/check-spec-symbol-derivation.mjs`, so that a reader who has seen
167+
* `normalizeFilterOperator` anywhere else in this tree cannot read the call
168+
* below as the same fold. Nothing about the behaviour moved with the name.
169+
*
170+
* The `?? op` tail is shared with the spec's, and load-bearing for the same
171+
* reason: an entry already written in AST form (`'='`, `'nin'`) has no row here
172+
* and must pass through. The `null` arm is NOT shared — the spec's hands a
173+
* non-string back verbatim (its body ends `return op as string`), this one
174+
* refuses, and `objectFilterEntryToAST` turns the refusal into a
175+
* `MalformedFilterError` instead of putting a number in the operator slot of a
176+
* tuple it is about to send. Both halves are pinned, in both directions, in
177+
* `scripts/__tests__/spec-symbol-ledger-data-objectstack-7265.test.ts`.
178+
*/
179+
function toAstFilterOperator(op: unknown): string | null {
150180
if (typeof op !== 'string') return null;
151181
const lower = op.toLowerCase();
152182
return FILTER_OPERATOR_ALIASES[lower] ?? FILTER_OPERATOR_ALIASES[op] ?? op;
@@ -487,7 +517,7 @@ function objectFilterEntryToAST(entry: any): [string, string, any] | null {
487517
// `name`-keyed rule cannot be saved as view metadata in the first place.
488518
const field = entry.field;
489519
const rawOp = entry.operator ?? entry.op ?? '=';
490-
const op = normalizeFilterOperator(rawOp);
520+
const op = toAstFilterOperator(rawOp);
491521
if (!field || !op) return null;
492522
return [String(field), op, entry.value];
493523
}

‎packages/data-objectstack/src/spec-symbol-batch6.test.ts‎

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@
1919
* MetadataSaveOptions → MetadataClientSaveOptions
2020
* ValidationError → DataApiValidationError
2121
*
22+
* A fourth rename joined the table later, from a different ledger:
23+
* `normalizeFilterOperator` → `toAstFilterOperator` (objectui#7265, rule 1's
24+
* DEBT block in `scripts/check-spec-symbol-derivation.mjs`). It is here because
25+
* the guard a rename needs is this one, whichever ledger sent it; what is
26+
* specific to it -- the site, the block, and the behaviour that refused BIND --
27+
* is in `scripts/__tests__/spec-symbol-ledger-data-objectstack-7265.test.ts`.
28+
*
2229
* The batch's own triage note said "data-objectstack is a direct client of the
2330
* spec protocol, so `SecurityPolicy` / `DroppedFieldsEvent` are probably hand
2431
* copies — derive them first". Half of that held: `DroppedFieldsEvent` was
@@ -143,6 +150,20 @@ const RENAMES: Array<[local: string, formerly: string, specMeaning: string]> = [
143150
'ValidationError',
144151
'a plain { field, message, code? } entry in a validation report',
145152
],
153+
// objectui#7265's `@object-ui/data-objectstack` slice. Appended here rather
154+
// than given a file of its own: this is the package's spec-symbol parity file
155+
// and the shape it already holds is exactly the one a rename needs. Unlike the
156+
// three above, the renamed symbol is a FUNCTION and a module-local one, so the
157+
// probe above has to see VALUE exports as well as types -- it does, it reads
158+
// the checker's `getExportsOfModule`, and the assertion below would go quiet
159+
// rather than red if it ever stopped.
160+
[
161+
'toAstFilterOperator',
162+
'normalizeFilterOperator',
163+
"folding an authored spelling to the canonical VIEW vocabulary (`eq` -> `equals`), the "
164+
+ '`z.preprocess` step on `ViewFilterRuleSchema.operator` -- NOT a translation to the '
165+
+ 'filter-AST symbols this package emits',
166+
],
146167
];
147168

148169
describe('renamed local concepts do not collide with a spec export', () => {

‎packages/plugin-view/src/config/__tests__/view-operator-builder-parity.test.ts‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
* View operator → FilterBuilder operator parity (#2901, #2945).
1111
*
1212
* The third of objectui's spec-operator translation tables. The other two —
13-
* `ListView.mapOperator` and `data-objectstack`'s `normalizeFilterOperator` —
14-
* were pinned to the spec in #2974, which found eight spellings they had missed
15-
* by enumerating instead of deriving. This one maps the same vocabulary onto the
16-
* FilterBuilder's operator ids, and it had missed nine:
13+
* `ListView.mapOperator` and `data-objectstack`'s `toAstFilterOperator` (spelled
14+
* `normalizeFilterOperator` until objectui#7265 renamed it off the spec's own
15+
* export of that name) — were pinned to the spec in #2974, which found eight
16+
* spellings they had missed by enumerating instead of deriving. This one maps
17+
* the same vocabulary onto the FilterBuilder's operator ids, and it had missed
18+
* nine:
1719
*
1820
* not_equals, greater_than, less_than, greater_than_or_equal,
1921
* less_than_or_equal, starts_with, ends_with, is_null, is_not_null

0 commit comments

Comments
 (0)