Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 | 23x 23x 613x 1263x 3x 1260x 1x 156x 734x 734x 577x 577x 296x 296x 296x 457x 248x 48x 48x 48x 161x 161x 48x 505x 505x 1763x 1763x 1263x 1263x 1263x 802x 457x 301x 156x 156x 156x 156x 500x 589x 589x 385x 204x 204x | import { type Value, fromJs } from '@ydbjs/value'
// ──────────────────────────────────────────────────────────────────────────
// Internal markers
// ──────────────────────────────────────────────────────────────────────────
const SymbolUnsafe = Symbol('unsafe')
const SymbolFragment = Symbol('fragment')
// ──────────────────────────────────────────────────────────────────────────
// Value helpers
// ──────────────────────────────────────────────────────────────────────────
function isObject(value: unknown): boolean {
return typeof value === 'object' && value !== null && !Array.isArray(value)
}
// Enhanced validation with detailed error messages for better debugging
function validateValue(value: unknown, index: number): void {
if (value === undefined) {
throw new Error(
`❌ Undefined value at position ${index} in yql template. ` +
`This usually means:\n` +
` • A variable wasn't initialized\n` +
` • A function returned undefined\n` +
` • An object property doesn't exist\n` +
`For intentional null database values, use YDB Optional type.`
)
}
if (value === null) {
throw new Error(
`❌ Null value at position ${index} in yql template. ` +
`JavaScript null is not directly supported in YDB queries.\n` +
`For null database values, use YDB Optional type instead.`
)
}
}
function toYdbValue(value: any): Value {
return isObject(value) && 'type' in value && 'kind' in (value as any)['type']
? (value as Value)
: fromJs(value as any)
}
// ──────────────────────────────────────────────────────────────────────────
// Raw SQL: identifiers and unsafe injection
// ──────────────────────────────────────────────────────────────────────────
export class UnsafeString extends String {
[SymbolUnsafe] = true
}
export function unsafe(value: string | { toString(): string }) {
return new UnsafeString(value.toString())
}
export function identifier(path: string) {
// Escape backticks inside identifier by doubling them
// Example: my`table -> my``table
let escaped = path.replaceAll('`', '``')
return unsafe('`' + escaped + '`')
}
// ──────────────────────────────────────────────────────────────────────────
// Composable fragments
// ──────────────────────────────────────────────────────────────────────────
/**
* A composable, non-executable piece of a query: its own template text plus
* bound values, which may themselves be other fragments, `UnsafeString`s, or
* scalars. Parameter names are assigned only when the fragment is flattened
* into a query, so fragments nest without parameter-name collisions.
*
* Create with {@link fragment} or {@link join}; splice into a `yql`/`fragment`
* template like any other interpolated value.
*/
export class Fragment {
[SymbolFragment] = true
constructor(
readonly strings: readonly string[],
readonly values: readonly unknown[]
) {}
}
function isFragment(value: unknown): value is Fragment {
return isObject(value) && (value as any)[SymbolFragment] === true
}
/**
* Create a composable query {@link Fragment} from a tagged template. Unlike the
* query client's `sql\`\``, a fragment is not executable — it only nests into
* another `yql`/`fragment` template or {@link join}.
*
* @example ```ts
* const cond = fragment`${identifier('age')} > ${18}`
* sql`SELECT * FROM users WHERE ${cond}`
* ```
*/
export function fragment<P extends any[] = unknown[]>(
strings: TemplateStringsArray,
...values: P
): Fragment {
return new Fragment(strings as readonly string[], values)
}
/**
* Combine fragments into one, interleaving a separator between them. An empty
* list yields an empty fragment; a single fragment is returned without a
* separator. The separator is structural SQL — a string is inserted as raw text.
*
* @example ```ts
* const where = join(conditions, ' AND ')
* sql`SELECT * FROM users WHERE ${where}`
* ```
*/
export function join(fragments: readonly Fragment[], separator: Fragment | string = ''): Fragment {
let sep = typeof separator === 'string' ? unsafe(separator) : separator
let values: unknown[] = []
for (let i = 0; i < fragments.length; i++) {
if (i > 0) values.push(sep)
values.push(fragments[i])
}
return new Fragment(new Array(values.length + 1).fill(''), values)
}
// ──────────────────────────────────────────────────────────────────────────
// Query building
// ──────────────────────────────────────────────────────────────────────────
// Single recursive pass over the template tree. A shared counter assigns
// `$p0..$pN` in traversal order across nested fragments — no renumbering.
function flatten(
strings: readonly string[],
values: readonly unknown[],
params: Record<string, Value>,
counter: { n: number }
): string {
let text = ''
for (let i = 0; i < strings.length; i++) {
text += strings[i]
if (i >= values.length) continue
let value = values[i]
validateValue(value, i)
if ((value as any)[SymbolUnsafe]) {
text += (value as UnsafeString).toString()
} else if (isFragment(value)) {
text += flatten(value.strings, value.values, params, counter)
} else {
let name = `$p${counter.n}`
params[name] = toYdbValue(value)
text += name
counter.n++
}
}
return text
}
export function yql<P extends any[] = unknown[]>(
strings: string | TemplateStringsArray,
...values: P
): { text: string; params: Record<string, Value> } {
let params: Record<string, Value> = Object.assign({}, null)
if (typeof strings === 'string') {
return { text: strings, params }
}
let text = flatten(strings, values, params, { n: 0 })
return { text, params }
}
|