8000 feat(mapper): type untyped JSON values as Json by colinhacks · Pull Request #151 · colinhacks/reylo · GitHub
[go: up one dir, main page]

Skip to content

feat(mapper): type untyped JSON values as Json - #151

Merged
colinhacks merged 6 commits into
mainfrom
json-type
Nov 1, 2026
Merged

colinhacks merged 6 commits into
mainfrom
json-type

Conversation

@colinhacks
@colinhacks colinhacks commented Nov 1, 2026 •
8000
Copy link
Copy Markdown
Owner

Types a JSON value that nothing constrains as Json, a type reylo now exports, instead of unknown:

type Json = string | number | boolean | null | { [key: string]: Json | undefined } | Json[];
  • Results. A json or jsonb value with no JSON Schema CHECK and no shape from the statement is Json on Postgres, MySQL and DuckDB: a column, ->, jsonb_set, jsonb_each's value, '{}'::jsonb, a column through a union or *. So is a value Reylo cannot type inside JSON a statement builds, such as SQLite's json_extract inside json_object. A nullable one reads Json, which already admits null. Values that may not be JSON stay unknown: SQLite's top-level json_extract, -> and ->>, a SQLite column with no declared type, DuckDB's VARIANT, MySQL's GEOMETRY, and a parameter nothing types.
  • Parameters. A JSON parameter takes Json, so a row's JSON goes back in as it came out. Where the statement takes no SQL NULL for it, as for a not null column, .run() binds null as JSON's null instead of SQL NULL, which the database refused. Left out there, it is refused before the statement runs, as every other such parameter has been since fix: refuse null for a parameter the statement takes no null for #75. A typed JSON parameter whose schema admits null follows the same rule.
  • Modules import Json from reylo only where one of their types names it. The undocumented generic that typed a row as JSON, Json<T>, is renamed AsJson<T>.
  • No engine change. The engine already gives these values an untyped JSON type, and its JSON Schema, {}, already admits any JSON value.
  • Docs. Each dialect page and the inference tables say Json, and the Postgres page shows JSON's null for a parameter. The capture is redone, and the skill says how to read a Json value.
  • Tests. tests/json.test.ts covers Postgres, MySQL and SQLite through the content mapper and tsc, beside updated render, values, execute, DuckDB and type-format tests. The driver matrix's new S1i scenario checks JSON's null on every driver: 5183 rows, with the same 4 failures RESULTS.md recorded before. lab/pg-forms and lab/mysql assert Json.

What stops compiling (TypeScript 7.1, strict):

  • row.payload as Settings, where Settings is an interface, fails with TS2352. The same cast to a type alias compiles, and so does as unknown as Settings.
  • A JSON parameter refuses a value typed unknown, an object typed by an interface, a readonly array variable, a Date, a Map and a Record<string, unknown>.

What runs differently:

  • A coalesce or a null test of a parameter doesn't let it take SQL NULL, for JSON as for every other type, so null there is now JSON's null, where an untyped JSON parameter used to pass SQL NULL. update docs set meta = coalesce(:meta::jsonb, meta) given { meta: null } now writes JSON's null instead of keeping meta, and where (:f::jsonb is null or payload @> :f::jsonb) given { f: null } no longer turns the filter off. Cast the parameter to nullable(jsonb) (nullable(json) on MySQL and SQLite) to pass SQL NULL; the Postgres, MySQL and SQLite pages, the inference section and the skill say so, and tests/json.test.ts checks both on each of the three. DuckDB is unchanged: a parameter passed to coalesce or tested for NULL takes null there already.

A json or jsonb value nothing constrains, and a value nothing types inside JSON a statement builds, is the exported Json type rather than unknown. A JSON parameter's null is JSON's null where the statement takes no SQL NULL for it.
Copilot AI balanced review requested due to automatic review settings November 1, 2026 17:59
@vercel
vercel Bot commented Nov 1, 2026 •
Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
reylo Ready Ready Preview Nov 1, 2026 6:40pm UTC

Request Review

@pullfrog pullfrog Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Important

One runtime change still compiles but writes different data: when a JSON parameter's only null handling is a coalesce or is null test, it now binds JSON's null where it used to bind SQL NULL. Details are inline.

Reviewed changes

I read the whole diff at f62b2b4: the new Json type, how it is rendered and imported, the change to JSON-null binding at run time, and the docs, labs and tests that go with them.

  • Json type: reylo now exports Json. The old Json<T> is renamed AsJson<T>. Under strict tsc, AsJson<Json> still resolves.
  • Rendering: a json value with no schema, a schema of {}, an untyped value inside built JSON and an open index signature are now written as Json, not unknown. The new orNull helper skips adding | null to Json.
  • Module import: reyloImport imports Json only when a generated module's Params or Row names it. It skips comments, string literals and keys named Json.
  • Run time: a JSON parameter that the statement takes no null for now binds null as JSON's null (JSON_NULL), and is refused when left out. This holds for typed and untyped JSON parameters on every driver path.
  • Docs, skill, capture and labs: the dialect pages and inference tables say Json. The Postgres page gains a "JSON null" example, and the driver matrix gains scenario S1i.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using claude-opus-5-5 | 𝕏

Comment thread src/execute.ts Resolved
Copilot AI left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Comment stripping misinterprets delimiters inside quoted names, erasing valid type references and omitting required imports, which leaves generated modules with unresolved types.

1 open issue
What changed in this PR

This PR gives unconstrained JSON values an exported Json type instead of unknown, updating Reylo’s generated types and parameter binding without changing the engine.

Changes:

  • Adds Json, renames the row-conversion generic to AsJson<T>, and imports Json only where needed.
  • Binds explicit null as JSON null for JSON parameters that reject SQL NULL, while rejecting omitted required values.
  • Updates regression tests, driver scenarios, documentation, and captured declarations.
File Description
tests/​values.test.ts Checks JSON decoding and array declarations.
tests/​type-format.test.ts Covers formatting types containing Json.
tests/​render.test.ts Updates JSON rendering expectations.
tests/​json.test.ts Adds mapper and JSON parameter integration coverage.
tests/​execute.test.ts Tests JSON null binding and missing parameters.
tests/​duckdb.test.ts Expects Json in DuckDB declarations.
src/​values.ts Maps JSON values to Json.
src/​schema.ts Updates a comment to name AsJson<T>.
src/​render.ts Renders JSON types and avoids redundant null unions.
src/​module-types.ts Detects required Json imports and updates nullability rendering.
src/​mapper-server.ts Emits conditional Json imports.
src/​index.ts Exports Json and renames the conversion generic.
src/​execute.ts Distinguishes JSON null from SQL NULL during binding.
skills/​using-reylo/​SKILL.md Explains JSON typing, narrowing, and null parameters.
site/​scripts/​schema-type.ts Uses Json for unconstrained schema values.
site/​scripts/​capture-types.ts Captures updated types and imports.
site/​lib/​sql-modules.json Refreshes captured declarations and JSON examples.
site/​lib/​playground.ts Updates playground nullability rendering.
site/​content/​sections/​03-inference.mdx Documents JSON inference and null binding.
site/​content/​docs/​sqlite.mdx Updates nested JSON fallback documentation.
site/​content/​docs/​postgres.mdx Documents Json and adds a JSON null example.
site/​content/​docs/​mysql.mdx Updates JSON typing and parameter guidance.
site/​content/​docs/​duckdb.mdx Documents Json results and null parameters.
lab/​pg-forms/​main.ts Updates Postgres type assertions to Json.
lab/​mysql/​main.ts Updates MySQL type assertions to Json.
lab/​driver-matrix/​lib/​harness.ts Adds cross-driver JSON null and omission checks.

Review effort: Balanced

Comment thread src/module-types.ts Outdated
* imports a name it does not use.
*/
export function reyloImport(...types: string[]): string {
const text = types.join("\n").replace(/\/\*[\s\S]*?\*\//g, "").replace(/"(?:[^"\\]|\\.)*"/g, '""');
Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 670c62a: reyloImport now reads string literals and comments in one left-to-right pass, so a comment delimiter inside a quoted name ("/*", "*/") starts no comment, and a quote inside a comment starts no string. tests/json.test.ts checks your example, which now imports Json, and two comments holding a stray quote.

@pullfrog pullfrog Bot left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found. The coalesce / null-test concern from the last review is now documented, and tests cover it on each dialect it applies to.

Reviewed changes

Since the prior Pullfrog review at f62b2b4, the author fixed how reyloImport reads strings and comments, documented how a JSON parameter passes SQL NULL, and added live tests for both forms.

  • Single-pass reyloImport scan: strings and comments are now matched in one left-to-right pass. A /* inside a quoted key no longer starts a comment, and a " inside a comment no longer starts a string. Three new assertions cover these cases.
  • Corrected JSON_NULL comment: the comment now says that a coalesce or null test of the parameter also takes no SQL NULL, and that nullable(jsonb) is the way to pass it.
  • Live coalesce checks: on Postgres, MySQL and SQLite, coalesce(:doc::json…) keeps JSON's null, and coalesce(:keep::nullable(json…)) swaps out SQL NULL. Both the declared types (keep?: Json) and the run-time values are asserted.
  • Docs and skill: the Postgres, MySQL and SQLite pages, the inference section and the skill now give the nullable(…) cast for passing SQL NULL. SQLite's page gains the JSON null rule, and the Postgres link to #parameters-that-may-be-null resolves.

Pullfrog  | View workflow run | Using claude-opus-5-5 | 𝕏

@colinhacks
colinhacks merged commit fc4fcd1 into main Nov 1, 2026
8 checks passed
@colinhacks
colinhacks deleted the json-type branch November 1, 2026 18:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

0