Skip to content

Instantly share code, notes, and snippets.

@alexesDev
Last active April 28, 2025 15:37
Show Gist options
  • Save alexesDev/5c1b3f544c545316c07fee5872443f0a to your computer and use it in GitHub Desktop.
Save alexesDev/5c1b3f544c545316c07fee5872443f0a to your computer and use it in GitHub Desktop.
https://mol.hyoo.ru + GraphQL v2
export class $yourproject_auth_email_form extends $.$yourproject_auth_email_form {
@$mol_mem
request_error(next?: string): string {
return next ?? ''
}
submit() {
const res = $yourproject_graphql_request(
/* GraphQL */ `
mutation RequestEmailSignInCode($input: RequestEmailSignInCodeInput!) {
data: requestEmailSignInCode(input: $input) {
... on ErrorPayload {
__typename
message
}
... on RequestEmailSignInCodePayload {
__typename
success
}
}
}
`,
{
input: {
email: this.email(),
},
}
)
if (res.data.__typename === 'ErrorPayload') {
this.request_error(res.data.message)
return
}
if (res.data.__typename === 'RequestEmailSignInCodePayload') {
if (res.data.success) {
this.$.$mol_state_arg.value('email', this.email())
return
}
}
this.request_error('Unknown error')
}
}

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

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

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

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

request.ts нужно будет реализовать под свой проект, с токенами в заголовкой и тп

typescript plugin генерирует $fragmentName в типах и mam пытается подклоючить такой пакет, его понятно нет. Так же в запросах скорее всего будет $input переменная, которая так же будет воспринята mam как пакет. Чтобы это обойти можно просто сделать в корне mam:

mkdir fragment
mkdir input

upd

Если запросы из какого-то файла не попадают в queries.ts - проверь запросы. В случае ошибки graphql-codegen молча такие скипает без ошибки.

upd request_document_cache.ts

У urql по-умолчанию работает сброс кеша на уровне __typename: https://nearform.com/open-source/urql/docs/basics/document-caching/

Попробовал у себя сделать так же, пока проблем не заметил. В файле request_document_cache.ts реализован простой кеш, который собирает __typename из всех query и триггерит перезагрузку когда мутация задевает связные __typename. Это может порождать лишние запросы, но в случае всяких админок позволяет положить болт на ручное обновление связных запросов (например если вы грузите список статей и отдельно где-то редактируется статья, после редактирования произойдет автоматическое обновление). Единственное нужно добавлять __typename в запросы везде, иначе этот трюк не сработает.

upd

Добавил добавление __typename на всех уровнях запроса.

module.exports = {
schema: 'http://localhost:8081/graphql',
documents: [__dirname + '/**/*.ts'],
pluckConfig: {
globalGqlIdentifierName: ['$yourproject_graphql_request'],
},
generates: {
[__dirname + '/graphql/queries.ts']: {
plugins: [
{
add: {
content: 'namespace $.$$ {\n\n',
},
},
'typescript',
'typescript-operations',
__dirname + '/-graphqlmol.js',
],
config: {
noExport: true,
molPrefix: '$yourproject_graphql', // FIX ME!
},
},
},
}
const { print, stripIgnoredCharacters } = require('graphql')
const { pascalCase } = require('change-case')
module.exports.plugin = (schema, documents, config) => {
const operations = []
const lines = []
const molPrefix = config.molPrefix || 'change_in_the_config'
for (const doc of documents) {
if (!doc.document) {
continue
}
for (const def of doc.document.definitions) {
if (def.kind !== 'OperationDefinition' || !def.name) continue
let prefix = 'Query'
if (def.operation === 'mutation') {
prefix = 'Mutation'
}
operations.push({
source: doc.rawSDL,
variablesType: `${def.name.value}${prefix}Variables`,
resultType: `${def.name.value}${prefix}`,
hasVars: (def.variableDefinitions?.length || 0) > 0,
})
}
}
for (const op of operations) {
const lit = op.source.replace(/\n/g, '\\n').replace(/\t/g, '\\t')
let vars = ''
let passVars = ''
if (op.hasVars) {
vars = `, variables: ${op.variablesType}`
passVars = `, variables`
}
lines.push(`export function ${molPrefix}_request(query: '${lit}'${vars}): ${op.resultType}`)
}
lines.push(
`export function ${molPrefix}_request(query: any, variables?: any) { return ${molPrefix}_raw_request(query, variables); }`
)
return lines.join('\n\n') + '\n\n}'
}
namespace $ {
export class $yourproject_graphql_error extends Error {
constructor(message: string, public detail?: unknown) {
super(message)
}
}
export function $yourproject_graphql_raw_request(query: string, variables?: any) {
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 $yourproject_graphql_error('GraphQL Error', res.errors)
}
return res.data
}
}
namespace $ {
export class $yourproject_graphql_error extends Error {
constructor(message: string, public detail?: unknown) {
super(message)
}
}
class cache extends $mol_object2 {
@$mol_mem_key
typename(key: string, val?: number) {
return val || 0
}
is_root(tn: string) {
// отключение передергивания для корневых запросов
return tn === 'Query' || tn === 'Mutation' || tn === 'AdminQuery' || tn === 'AdminMutation'
}
markTypenames(value: unknown, reset: boolean): void {
if (Array.isArray(value)) {
for (const item of value) this.markTypenames(item, reset)
} else if (value && typeof value === 'object') {
const obj = value as Record<string, unknown>
if (typeof obj.__typename === 'string' && !this.is_root(obj.__typename)) {
const nv = reset ? this.typename(obj.__typename) + 1 : undefined
this.typename(obj.__typename, nv)
}
for (const key in obj) {
this.markTypenames(obj[key], reset)
}
}
}
}
const cacheInstance = new cache()
function inject_typenames(source: string) {
let out = ''
let i = 0
const n = source.length
let state: 'default' | 'string' | 'blockString' | 'comment' = 'default'
let quoteChar: '"' | "'" | null = null // для string / blockString
let blockQuoteLen = 0 // 1 или 3 кавычки
while (i < n) {
const c = source[i]
const next2 = source.slice(i, i + 2)
const next3 = source.slice(i, i + 3)
/* ---------- переходы состояний ---------- */
if (state === 'default') {
if (c === '#') {
// начало комментария
state = 'comment'
out += c
i++
continue
}
if (next3 === '"""' || next3 === "'''") {
// начало блок-строки
state = 'blockString'
quoteChar = c as '"' | "'"
blockQuoteLen = 3
out += next3
i += 3
continue
}
if (c === '"' || c === "'") {
// начало обычной строки
state = 'string'
quoteChar = c as '"' | "'"
blockQuoteLen = 1
out += c
i++
continue
}
if (c === '{') {
// наша целевая точка
out += c
i++
// пропускаем пробелы/переводы строк/запятые
let j = i
while (j < n && /[\s,\r\n]/.test(source[j])) j++
const hasTypename = source.startsWith('__typename', j)
if (!hasTypename) out += ' __typename'
continue
}
} else if (state === 'comment') {
out += c
i++
if (c === '\n') state = 'default'
continue
} else if (state === 'string') {
out += c
i++
if (c === quoteChar && source[i - 2] !== '\\') state = 'default'
continue
} else if (state === 'blockString') {
out += c
i++
if (source.slice(i - blockQuoteLen, i) === quoteChar!.repeat(blockQuoteLen)) state = 'default'
continue
}
/* default path */
out += c
i++
}
return out
}
export function $yourproject_graphql_raw_request(query: string, variables?: any) {
const res = $.$mol_fetch.json('/graphql', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: inject_typenames(query), variables }),
}) as { data?: any; errors?: any[] }
if (res.errors) {
throw new $.$yourproject_graphql_error('GraphQL Error', res.errors)
}
const isMutation = !!query.match(/^\s+mutation/)
cacheInstance.markTypenames(res.data, isMutation)
return res.data
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment