insert into users (id, name) ... works, but the equally common insert into users(id, name) ... (no space before the paren) is parsed as if the table were named users(id,.
WordAfterKeyword<S, 'into'> takes the first space-delimited word after INTO, which is users(id,. CleanTargetIdentifier doesn't split on (, so the source table becomes users(id,. InsertColumnList then drops the same first word and looks for a ( in name) values ..., finds none, and resolves to never — which is why the parameter tuple collapses to the untyped fallback.
Repro
interface DB { users: { id: number; name: string } }
type P = Params<DB, 'insert into users(id, name) values ($1, $2)'>;
// ^? unknown[] expected [number, string]
type R = StrictRow<DB, 'insert into users(id, name) values ($1, $2) returning id'>;
// ^? QueryTypeError<'unknown table: users(id,'> expected { id: number }
With a space (insert into users (id, name) ...) both are correct, so the failure is purely whitespace-driven and gives no hint about what to change.
Suggested fix
Split the target identifier at the first ( before CleanTargetIdentifier runs (both in SingleSource<WordAfterKeyword<S, 'into'>> and in InsertColumnList), so users(id, yields the table users and leaves (id, name) values ... for the column-list scan. The same applies to merge into t(...), and the values( spelling on the other side of the statement is already handled by ScanValuesGroups' Trim<S> extends \(${infer AfterOpen}``.
Found by the 2026-07-28 post-#243 audit on master @ 337d78a.
insert into users (id, name) ...works, but the equally commoninsert into users(id, name) ...(no space before the paren) is parsed as if the table were namedusers(id,.WordAfterKeyword<S, 'into'>takes the first space-delimited word afterINTO, which isusers(id,.CleanTargetIdentifierdoesn't split on(, so the source table becomesusers(id,.InsertColumnListthen drops the same first word and looks for a(inname) values ..., finds none, and resolves tonever— which is why the parameter tuple collapses to the untyped fallback.Repro
With a space (
insert into users (id, name) ...) both are correct, so the failure is purely whitespace-driven and gives no hint about what to change.Suggested fix
Split the target identifier at the first
(beforeCleanTargetIdentifierruns (both inSingleSource<WordAfterKeyword<S, 'into'>>and inInsertColumnList), sousers(id,yields the tableusersand leaves(id, name) values ...for the column-list scan. The same applies tomerge into t(...), and thevalues(spelling on the other side of the statement is already handled byScanValuesGroups'Trim<S> extends \(${infer AfterOpen}``.Found by the 2026-07-28 post-#243 audit on master @ 337d78a.