Repository navigation
feat(mapper): type untyped JSON values as Json - #151
Conversation
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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
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.
Jsontype:reylonow exportsJson. The oldJson<T>is renamedAsJson<T>. Under stricttsc,AsJson<Json>still resolves.- Rendering: a
jsonvalue with no schema, a schema of{}, an untyped value inside built JSON and an open index signature are now written asJson, notunknown. The neworNullhelper skips adding| nulltoJson. - Module import:
reyloImportimportsJsononly when a generated module'sParamsorRownames it. It skips comments, string literals and keys namedJson. - Run time: a JSON parameter that the statement takes no null for now binds
nullas JSON'snull(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.
claude-opus-5-5 | 𝕏
There was a problem hiding this comment.
🟡 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 toAsJson<T>, and importsJsononly where needed. - Binds explicit
nullas 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
| * imports a name it does not use. | ||
| */ | ||
| export function reyloImport(...types: string[]): string { | ||
| const text = types.join("\n").replace(/\/\*[\s\S]*?\*\//g, "").replace(/"(?:[^"\\]|\\.)*"/g, '""'); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
✅ 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
reyloImportscan: 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_NULLcomment: the comment now says that acoalesceor null test of the parameter also takes no SQLNULL, and thatnullable(jsonb)is the way to pass it. - Live
coalescechecks: on Postgres, MySQL and SQLite,coalesce(:doc::json…)keeps JSON'snull, andcoalesce(:keep::nullable(json…))swaps out SQLNULL. 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 SQLNULL. SQLite's page gains the JSON null rule, and the Postgres link to#parameters-that-may-be-nullresolves.
claude-opus-5-5 | 𝕏


Types a JSON value that nothing constrains as
Json, a typereylonow exports, instead ofunknown:jsonorjsonbvalue with no JSON SchemaCHECKand no shape from the statement isJsonon Postgres, MySQL and DuckDB: a column,->,jsonb_set,jsonb_each's value,'{}'::jsonb, a column through aunionor*. So is a value Reylo cannot type inside JSON a statement builds, such as SQLite'sjson_extractinsidejson_object. A nullable one readsJson, which already admits null. Values that may not be JSON stayunknown: SQLite's top-leveljson_extract,->and->>, a SQLite column with no declared type, DuckDB'sVARIANT, MySQL'sGEOMETRY, and a parameter nothing types.Json, so a row's JSON goes back in as it came out. Where the statement takes no SQL NULL for it, as for anot nullcolumn,.run()bindsnullas JSON'snullinstead 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.Jsonfromreyloonly where one of their types names it. The undocumented generic that typed a row as JSON,Json<T>, is renamedAsJson<T>.{}, already admits any JSON value.Json, and the Postgres page shows JSON's null for a parameter. The capture is redone, and the skill says how to read aJsonvalue.tests/json.test.tscovers Postgres, MySQL and SQLite through the content mapper andtsc, 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 failuresRESULTS.mdrecorded before.lab/pg-formsandlab/mysqlassertJson.What stops compiling (TypeScript 7.1,
strict):row.payload as Settings, whereSettingsis aninterface, fails with TS2352. The same cast to atypealias compiles, and so doesas unknown as Settings.unknown, an object typed by aninterface, areadonlyarray variable, aDate, aMapand aRecord<string, unknown>.What runs differently:
coalesceor a null test of a parameter doesn't let it take SQL NULL, for JSON as for every other type, sonullthere is now JSON'snull, where an untyped JSON parameter used to pass SQL NULL.update docs set meta = coalesce(:meta::jsonb, meta)given{ meta: null }now writes JSON'snullinstead of keepingmeta, andwhere (:f::jsonb is null or payload @> :f::jsonb)given{ f: null }no longer turns the filter off. Cast the parameter tonullable(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, andtests/json.test.tschecks both on each of the three. DuckDB is unchanged: a parameter passed tocoalesceor tested forNULLtakesnullthere already.