Skip to content

Instantly share code, notes, and snippets.

@alexesDev
Last active April 22, 2025 13:26
Show Gist options
  • Save alexesDev/8925ceed852785d70b6b1920a9072ebd to your computer and use it in GitHub Desktop.
Save alexesDev/8925ceed852785d70b6b1920a9072ebd to your computer and use it in GitHub Desktop.
GraphQL https://mol.hyoo.ru/ integration
namespace $.$$ {
/* GraphQL */ `
query ListUsers {
admin {
listUsers {
nodes { id, email, createdAt }
}
}
}
`
// codegen можно запустить в режиме watch
// и как только увидит запрос выже - $trip2g_graphql_list_users() уже будет готова
export class $trip2g_users extends $.$trip2g_users {
@$mol_mem
rows() {
const res = $trip2g_graphql_list_users()
return res.admin.listUsers.nodes
}
// ...
}
}

Создать примерно такую структуру

yourproject/
  graphql
    request.ts # Файл ниже, базовая функция запроса
    queries.ts # Будет создан код геном
    graphqlgen.js # Конфиг кодгена
    graphqlmol.js # Плагин под $mol_data

После вызовы кодгена

npx graphql-codegen --config trip2g/graphql/graphqlgen.js

Из всего проекте соберутся запросы такого вида:

	/* GraphQL */ `
		query ListUsers {
			admin {
				listUsers {
					nodes { id, email, createdAt }
				}
			}
		}
	`

/* GraphQL */ это magic comment метка запроса. После вызова кодгена будут доступны типизированные функции запросов:

  const test_data = $trip2g_graphql_list_users();
  console.log(test_data.admin.listUsers.nodes);

graphqlmol.js очень сырой и подразумевается, что вы докрутите его под свой проект. Там не так много кода, чтобы заворачивать в какой-то решение. Написан левой ногой с o3, но мне пока достаточно.

module.exports = {
schema: 'http://localhost:8081/graphql',
documents: [__dirname + '/../**/*.ts'],
generates: {
[__dirname + '/queries.ts']: {
plugins: [__dirname + '/graphqlmol.js'],
},
},
}
const { print, getNamedType, isScalarType, isListType, isNonNullType, Kind } = require('graphql')
const TS_SCALAR = {
String: 'string',
ID: 'string',
Int: 'number',
Int64: 'number',
Float: 'number',
Boolean: 'boolean',
}
const PARSER_SCALAR = {
String: '$mol_data_string',
ID: '$mol_data_string',
Int: '$mol_data_integer',
Int64: '$mol_data_integer',
Float: '$mol_data_number',
Time: '$mol_data_pipe( $mol_data_string , $mol_time_moment )',
Boolean: '$mol_data_boolean',
}
function snake(str) {
return str
.replace(/([a-z0-9])([A-Z])/g, '$1_$2')
.replace(/[-\s]+/g, '_')
.toLowerCase()
}
function unwrapNonNull(type) {
let t = type
while (isNonNullType(t)) {
t = t.ofType
}
return t
}
function genParser(type, sel, depth = 0) {
const nullable = !isNonNullType(type)
const core = unwrapNonNull(type)
const isRoot = depth === 0
if (isListType(core)) {
const inner = genParser(core.ofType, sel, depth + 1)
const arr = `$mol_data_array(${inner})`
return nullable ? `$mol_data_optional(${arr})` : arr
}
if (isScalarType(core)) {
const base = PARSER_SCALAR[core.name] || '$mol_data_string'
return nullable ? `$mol_data_optional(${base})` : base
}
const indent = '\t'.repeat(depth + 2)
const outdent = '\t'.repeat(depth + 1)
const fields = sel.selections
.filter(s => s.kind === Kind.FIELD)
.map(s => {
const fieldName = s.name.value
// используем исходный type, чтобы взять NonNull или нет
const fieldDef = getNamedType(type).getFields()[fieldName]
const inner = genParser(fieldDef.type, s.selectionSet || { selections: [] }, depth + 1)
const wrapped = isNonNullType(fieldDef.type) ? inner : `$mol_data_optional(${inner})`
return `${indent}${fieldName}: ${wrapped}`
})
.join(',\n')
const rec = `$mol_data_record({\n${fields}\n${outdent}})`
return nullable && !isRoot ? `$mol_data_optional(${rec})` : rec
}
function genVarTs(type) {
const nullable = !isNonNullType(type)
const core = unwrapNonNull(type)
let t
if (isListType(core)) {
t = `${genVarTs(core.ofType)}[]`
} else {
t = TS_SCALAR[core.name] || 'any'
}
return nullable ? `${t} | undefined` : t
}
module.exports = {
plugin: (schema, docs) => {
const ops = docs
.flatMap(d => d.document.definitions)
.filter(
d => d.kind === Kind.OPERATION_DEFINITION && (d.operation === 'query' || d.operation === 'mutation')
)
const out = ['namespace $.$$ {', '']
for (const op of ops) {
const name = op.name.value
const snk = snake(name)
const Q = `$trip2g_graphql_${snk}_query`
const R = `$trip2g_graphql_${snk}_response`
const V = `$trip2g_graphql_${snk}_variables`
const F = `$trip2g_graphql_${snk}`
out.push(`\texport const ${Q} = \`${print(op).replace(/`/g, '\\`')}\``, '')
const vars = op.variableDefinitions ?? []
if (vars.length) {
const body = vars.map(v => `\t\t${v.variable.name.value}: ${genVarTs(v.type)}`).join(',\n')
out.push(`\texport type ${V} = {\n${body}\n\t}`, '')
}
const root = op.operation === 'query' ? schema.getQueryType() : schema.getMutationType()
out.push(`\texport const ${R} = ${genParser(root, op.selectionSet)}`, '')
if (vars.length) {
out.push(
`\texport const ${F} = (variables: ${V}) =>`,
`\t\t${R}($trip2g_graphql_request(${Q}, variables))`,
''
)
} else {
out.push(`\texport const ${F} = () =>`, `\t\t${R}($trip2g_graphql_request(${Q}))`, '')
}
}
out.push('}')
return out.join('\n')
},
}
namespace $.$$ {
export const $trip2g_graphql_list_users_query = `query ListUsers {
admin {
listUsers {
nodes {
id
email
createdAt
}
}
}
}`
export const $trip2g_graphql_list_users_response = $mol_data_record({
admin: $mol_data_record({
listUsers: $mol_data_record({
nodes: $mol_data_array($mol_data_record({
id: $mol_data_integer,
email: $mol_data_string,
createdAt: $mol_data_pipe( $mol_data_string , $mol_time_moment )
}))
})
})
})
export const $trip2g_graphql_list_users = () =>
$trip2g_graphql_list_users_response($trip2g_graphql_request($trip2g_graphql_list_users_query))
}
namespace $ {
export class $trip2g_graphql_error extends Error {
constructor(message: string, public detail?: unknown) {
super(message)
}
}
export function $trip2g_graphql_request<V = undefined>(query: string, variables?: V) {
const res = $.$mol_fetch.json('/graphql', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, variables }),
}) as { data?: any; errors?: any[] }
if (res.errors) {
throw new $.$trip2g_graphql_error('GraphQL Error', res.errors)
}
return res.data
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment