Skip to content
Open
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
5 changes: 5 additions & 0 deletions src/server/templates/typescript.ts
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,11 @@ export const apply = async ({
: ''

function generateNullableUnionTsType(tsType: string, isNullable: boolean) {
// The Json type already includes `null`, so a NOT NULL json/jsonb column has to be
// narrowed with NonNullable to reflect the database constraint. See supabase/postgres-meta#1055.
if (tsType === 'Json' && !isNullable) {
return `NonNullable<${tsType}>`
}
// Only add the null union if the type is not unknown as unknown already includes null
if (tsType === 'unknown' || tsType === 'any' || !isNullable) {
return tsType
Expand Down
43 changes: 43 additions & 0 deletions test/server/typegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6964,3 +6964,46 @@ test('typegen: python w/ excluded/included schemas', async () => {
})
}
})

test('typegen: typescript w/ NOT NULL json column narrows Json to NonNullable<Json>', async () => {
// The generated Json type includes `null`, so a NOT NULL json/jsonb column has to be
// narrowed with NonNullable to honor the database constraint. A nullable json column is
// left as-is because Json already covers null. See supabase/postgres-meta#1055.
await app.inject({
method: 'POST',
path: '/query',
payload: {
query: `
CREATE SCHEMA IF NOT EXISTS json_nullability_test;
CREATE TABLE IF NOT EXISTS json_nullability_test.documents (
id serial PRIMARY KEY,
required_metadata jsonb NOT NULL,
optional_metadata jsonb
);
`,
},
})

try {
const { body } = await app.inject({
method: 'GET',
path: '/generators/typescript',
query: { included_schemas: 'json_nullability_test' },
})
// NOT NULL jsonb narrows to NonNullable<Json> (Row and Insert).
expect(body).toContain('required_metadata: NonNullable<Json>')
expect(body).not.toContain('required_metadata: Json | null')
// Nullable jsonb is unchanged: Json already includes null.
expect(body).toContain('optional_metadata: Json | null')
} finally {
await app.inject({
method: 'POST',
path: '/query',
payload: {
query: `
DROP SCHEMA IF EXISTS json_nullability_test CASCADE;
`,
},
})
}
})