Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .changeset/spec-special-operator-describe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
---
'@objectstack/spec': patch
---

docs(spec): `SpecialOperator` gains the `.describe()` that puts `$null` / `$exists` on the reference page (#14048)

`SpecialOperatorSchema` declared both members as bare `z.boolean().optional()` with a
JSDoc comment and no `.describe()`. `content/docs/references/data/filter.mdx` fills its
Description column from `prop.description` — the JSON-Schema projection of a Zod
`.describe()` — so both cells rendered **empty**, and the published reference page said
nothing whatsoever about the two operators whose meaning was the subject of a six-site
correction campaign (#13539, #13709). A reader could not learn from that page that
`$exists` asks whether the field HAS A VALUE.

Each member now carries a `.describe()`; the JSDoc stays as the source of truth it
already was and the describe restates it. The regenerated page gains exactly two
Description cells (`filter.mdx:138-139`) and nothing else.

Prose only. No accept/reject or shape change: both members were and stay
`z.boolean().optional()`, so the only JSON-Schema delta is a `description` string on two
properties. Verified on a freshly built `dist`: `check:generated` reported
`content/docs/references/**` as the single stale artifact and `--fix` regenerated only
`filter.mdx`; `check:api-surface`, `check:export-origins` and `check:authorable-surface`
stayed green with no new export.

The wording was written against `scripts/check-corpus-claim-drift.mjs` rather than into
it: that shrink-only ratchet watches `content/docs/**` for `$exists` prose, its
`exists-key-presence` row reads `exists-key-presence 4` both before this diff and after
the page was regenerated, so the new prose adds zero claim sites.
4 changes: 2 additions & 2 deletions content/docs/references/data/filter.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,8 @@ Type: `[FilterArray](#filterarray)[]`

| Property | Type | Required | Description |
| :--- | :--- | :--- | :--- |
| **$null** | `boolean` | optional | |
| **$exists** | `boolean` | optional | |
| **$null** | `boolean` | optional | Is-null check. `true` matches rows where the field is null, `false` matches rows where it is not null. Lowered to `IS NULL` (true) / `IS NOT NULL` (false) on the SQL family and to `{ field: null }` (true) / `{ $ne: null }` (false) on MongoDB. |
| **$exists** | `boolean` | optional | Has-a-value check — the exact inverse of `$null`. `true` matches rows where the field holds a value (`!= null`), `false` matches rows where it holds none. Portable across every backend the platform ships: lowered to `IS NOT NULL` (true) / `IS NULL` (false) on the SQL family and to `{ $ne: null }` (true) / `{ $eq: null }` (false) on MongoDB. |


---
Expand Down
14 changes: 12 additions & 2 deletions packages/spec/src/data/filter.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1025,14 +1025,24 @@ export function likePatternToGlobPattern(pattern: string): string {
*/
export const SpecialOperatorSchema = lazySchema(() => z.object({
/** Is null check - SQL: IS NULL (true) / IS NOT NULL (false) | MongoDB: field: null */
$null: z.boolean().optional(),
$null: z.boolean().optional().describe(
'Is-null check. `true` matches rows where the field is null, `false` matches rows '
+ 'where it is not null. Lowered to `IS NULL` (true) / `IS NOT NULL` (false) on the '
+ 'SQL family and to `{ field: null }` (true) / `{ $ne: null }` (false) on MongoDB.'
),

/**
* Field HAS A VALUE (`!= null`) — the inverse of `$null`, never key presence.
* Lowered to `IS NOT NULL` (true) / `IS NULL` (false) on SQL and to
* `{$ne: null}` / `{$eq: null}` on MongoDB.
*/
$exists: z.boolean().optional(),
$exists: z.boolean().optional().describe(
'Has-a-value check — the exact inverse of `$null`. `true` matches rows where the '
+ 'field holds a value (`!= null`), `false` matches rows where it holds none. '
+ 'Portable across every backend the platform ships: lowered to `IS NOT NULL` (true) / '
+ '`IS NULL` (false) on the SQL family and to `{ $ne: null }` (true) / '
+ '`{ $eq: null }` (false) on MongoDB.'
),
}));

// ============================================================================
Expand Down
Loading