Skip to content

Instantly share code, notes, and snippets.

@pjlsergeant
Last active March 26, 2024 07:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pjlsergeant/99a1f387695e2da90c76598c244a7148 to your computer and use it in GitHub Desktop.
Save pjlsergeant/99a1f387695e2da90c76598c244a7148 to your computer and use it in GitHub Desktop.
/*
# WHAT
Do a pre-pass of tagged template literals, interpolating strings
# WHY
Consider Prisma's `$queryRaw()`; it accepts a tagged template literal, and
replaces all the templated values with SQL placeholders (`$1`, `$2`) etc, before
actually running the query. That's great, unless you want to additionally add
some values to the SQL that Prisma can't do that to, like an interval -- the
below code doesn't work, for this reason:
```
const result = await prisma.$queryRaw`
SELECT * FROM foo
WHERE foo.id = ${id}
AND foo.when >= CURRENT_DATE - INTERVAL '${interval}'`;
```
This package allows you to mark values that should be interpolated first, before
passing to the parent function, eg:
```
const result = await prisma.$queryRaw(...pp`
SELECT * FROM foo
WHERE foo.id = ${id}
AND foo.when >= CURRENT_DATE - INTERVAL '${p('1 week')}'`);
```
# FUNCTIONS
## pp\`\`
Accepts a tagged template literal, and returns one, with only values that have
been wrapped in `p()` interpolated.
# p(string)
Marks that a value should be interpolated first
*/
class WrappedValue { constructor( public v: string ) {} }
export function p(v: string) { return new WrappedValue(v) }
function _createTemplateStringsArray(strings: string[]): TemplateStringsArray {
const templateStringsArray = [...strings] as unknown as TemplateStringsArray;
Object.defineProperty(templateStringsArray, 'raw', {
value: Object.freeze([...strings]),
writable: false,
});
return Object.freeze(templateStringsArray);
}
export function pp<T = any>(strings: TemplateStringsArray, ...values: (T | WrappedValue)[]) : [TemplateStringsArray, ...T[]] {
const newStrings : string[] = [];
const newValues : any[] = [];
let mergeString : string | null = null;
for ( let i = 0; i < (strings.length - 1); i++ ) {
let leftString : string;
if ( mergeString !== null ) {
leftString = mergeString;
mergeString = null;
} else {
leftString = strings[i]
}
const value = values[i];
if ( typeof value === 'object' && value instanceof WrappedValue ) {
// We should always have another value left, because a value right at the
// end still gets an empty string appended
const rightString = strings[i+1];
mergeString = leftString + value.v + rightString;
} else {
newStrings.push( leftString );
newValues.push( value );
}
}
newStrings.push( mergeString === null ? strings.at(-1)! : mergeString );
return [_createTemplateStringsArray(newStrings), ...newValues];
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment