import { compress } from '../compress.js'; import { GQL_ENUM, GQL_TEMPLATE, isGqlEnum, isGqlFragment, isGqlTemplate, isGqlVariable } from './symbols.js'; import type { GqlEnum } from './enum.js'; import type { GqlFragment } from './fragment.js'; import type { TemplateStringsArrayLike } from './template-string.js'; import type { GqlVariable } from './variable.js'; /** GQL 模板可填充的类型 */ export type GqlTemplateValue = null | string | number | boolean | bigint | GqlEnum | GqlVariable | GqlFragment | GqlTemplate | GqlTemplate[]; /** GQL 模板已处理的填充类型 */ export type GqlTemplateContent = string | GqlVariable | GqlFragment | GqlTemplate | GqlTemplate[]; /** GQL 模板 */ export interface GqlTemplate { /** 插值量 */ [GQL_TEMPLATE]: readonly GqlTemplateContent[]; /** 字面量 */ parts: readonly string[]; } /** 检查模板字符串 */ function checkTemplatePart(part: string): void { const fq = part.indexOf('"'); const lq = part.lastIndexOf('"'); if (fq >= 0 && fq === lq) { throw new Error( `Invalid GQL template, interpolation is not allowed in strings, instead of \`input: "interpolate \${value}" \` use \`input: \${\`interpolate \${value}\`} \``, ); } } /** 构造插值变量 */ function stringify(value: unknown): string { if (value == null) return 'null'; // eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check switch (typeof value) { case 'boolean': case 'number': case 'bigint': return value.toString(); case 'string': return JSON.stringify(value); default: // "symbol" | "object" | "function" } if (isGqlEnum(value)) return value[GQL_ENUM]; throw new Error( `Cannot stringify ${Object.prototype.toString.call( value, )} as GQL literal, please use GqlVariable() to wrap it as a GQL variable.`, ); } /** 创建 GQL 模板 */ export function GqlTemplate(query: TemplateStringsArrayLike, args: readonly GqlTemplateValue[]): GqlTemplate { if (args.length + 1 !== query.raw.length) { throw new Error(`Query string length does not match args.`); } const parts: string[] = []; const interpolated: GqlTemplateContent[] = []; let index = 0; for (const part of query.raw) { checkTemplatePart(part); parts.push(compress(part)); if (index < args.length) { const arg = args[index]; if (isGqlVariable(arg)) { interpolated.push(arg); } else if (isGqlFragment(arg)) { interpolated.push(arg); } else if (isGqlTemplate(arg) || (Array.isArray(arg) && arg.every((item) => isGqlTemplate(item)))) { interpolated.push(arg); } else { interpolated.push(stringify(arg)); } } index++; } return { [GQL_TEMPLATE]: interpolated, parts, }; }