You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Simply creating a new sql function, as prescribed, results in Sql instances where the values property is still the built-in Value type, which is just an alias for unknown - which doesn't actually work, e.g. when you try to pass this to a (properly typed) SQL client library.
Similarly, this doesn't give you join, bulk, raw (etc.) functions with the correct types.
To address this, you'd need some sort of factory function for the whole API, I think?
I tried this:
exportfunctionsetup<Value>(){/** * Supported value or SQL instance. */typeRawValue=Value|Sql;/** * A SQL instance can be nested within each other to build SQL strings. */classSql{readonlyvalues: Value[];readonlystrings: string[];constructor(rawStrings: readonlystring[],rawValues: readonlyRawValue[]){if(rawStrings.length-1!==rawValues.length){if(rawStrings.length===0){thrownewTypeError("Expected at least 1 string");}thrownewTypeError(`Expected ${rawStrings.length} strings to have ${rawStrings.length-1} values`,);}constvaluesLength=rawValues.reduce<number>((len,value)=>len+(valueinstanceofSql ? value.values.length : 1),0,);this.values=newArray(valuesLength);this.strings=newArray(valuesLength+1);this.strings[0]=rawStrings[0];// Iterate over raw values, strings, and children. The value is always// positioned between two strings, e.g. `index + 1`.leti=0,pos=0;while(i<rawValues.length){constchild=rawValues[i++];constrawString=rawStrings[i];// Check for nested `sql` queries.if(childinstanceofSql){// Append child prefix text to current string.this.strings[pos]+=child.strings[0];letchildIndex=0;while(childIndex<child.values.length){this.values[pos++]=child.values[childIndex++];this.strings[pos]=child.strings[childIndex];}// Append raw string to current string.this.strings[pos]+=rawString;}else{this.values[pos++]=child;this.strings[pos]=rawString;}}}getsql(){constlen=this.strings.length;leti=1;letvalue=this.strings[0];while(i<len)value+=`?${this.strings[i++]}`;returnvalue;}getstatement(){constlen=this.strings.length;leti=1;letvalue=this.strings[0];while(i<len)value+=`:${i}${this.strings[i++]}`;returnvalue;}gettext(){constlen=this.strings.length;leti=1;letvalue=this.strings[0];while(i<len)value+=`$${i}${this.strings[i++]}`;returnvalue;}inspect(){return{sql: this.sql,statement: this.statement,text: this.text,values: this.values,};}}/** * Create a SQL query for a list of values. */functionjoin(values: readonlyRawValue[],separator=",",prefix="",suffix="",){if(values.length===0){thrownewTypeError("Expected `join([])` to be called with an array of multiple elements, but got an empty array",);}returnnewSql([prefix, ...Array(values.length-1).fill(separator),suffix],values,);}/** * Create a SQL query for a list of structured values. */functionbulk(data: ReadonlyArray<ReadonlyArray<RawValue>>,separator=",",prefix="",suffix="",){constlength=data.length&&data[0].length;if(length===0){thrownewTypeError("Expected `bulk([][])` to be called with a nested array of multiple elements, but got an empty array",);}constvalues=data.map((item,index)=>{if(item.length!==length){thrownewTypeError(`Expected \`bulk([${index}][])\` to have a length of ${length}, but got ${item.length}`,);}returnnewSql(["(", ...Array(item.length-1).fill(separator),")"],item);});returnnewSql([prefix, ...Array(values.length-1).fill(separator),suffix],values,);}/** * Create raw SQL statement. */functionraw(value: string){returnnewSql([value],[]);}/** * Placeholder value for "no text". */constempty=raw("");/** * Create a SQL object from a template string. */functionsql(strings: readonlystring[],
...values: readonlyRawValue[]){returnnewSql(strings,values);}return{
sql, join, bulk, raw, empty
}}exportconst{ sql, join, bulk, raw, empty }=setup<unknown>();exportdefaultsql;
It's a simple change, but it's a breaking change, in that the exported types Value and RawValue are lost - not that these were useful (probably) since, again, they don't represent a strict value type, and they don't work with a custom sql wrapper function.
I could of course write my own wrapper module using unsafe typecasts, correcting all the types by force - but then I'm not really using the types provided by the package, and instead just throwing them all away and replacing them, which definitely feels wrong.
I don't know, what do you think?
As I recall, you don't use TS yourself, so maybe you don't care? ☺️
The text was updated successfully, but these errors were encountered:
This does somewhat simplify integration for modules with proper typing - for example, here's Deno's SQLite client integrated in a local db.ts module for type-safety:
import{Database,BindValue}from"jsr:@db/[email protected]";import{setup}from"./sql-template-tag.ts";// (my local copy, modified as shown above)exportconst{ sql, join, bulk, raw, empty }=setup<BindValue>();
On the other hand, this factory function doesn't actually do anything, other than provide types - I mean, it doesn't do anything at run-time, and so the extra function call seems kind of silly.
But I don't know of any other practical way to add generic type-safety to a whole API like this.
My first thought was to just have a generic Sql<TValue> class, but again, this fails to connect with the types in the rest of the API... so I don't know. 🤔
The Stricter TypeScript section of the README is a bit unhelpful:
Simply creating a new
sql
function, as prescribed, results inSql
instances where thevalues
property is still the built-inValue
type, which is just an alias forunknown
- which doesn't actually work, e.g. when you try to pass this to a (properly typed) SQL client library.Similarly, this doesn't give you
join
,bulk
,raw
(etc.) functions with the correct types.To address this, you'd need some sort of factory function for the whole API, I think?
I tried this:
It's a simple change, but it's a breaking change, in that the exported types
Value
andRawValue
are lost - not that these were useful (probably) since, again, they don't represent a strict value type, and they don't work with a customsql
wrapper function.I could of course write my own wrapper module using unsafe typecasts, correcting all the types by force - but then I'm not really using the types provided by the package, and instead just throwing them all away and replacing them, which definitely feels wrong.
I don't know, what do you think?
As I recall, you don't use TS yourself, so maybe you don't care?☺️
The text was updated successfully, but these errors were encountered: