Skip to content

Instantly share code, notes, and snippets.

@alexesDev
Last active April 28, 2025 15:37

Revisions

  1. alexesDev revised this gist Apr 28, 2025. 2 changed files with 6 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -32,4 +32,8 @@ mkdir input
    У urql по-умолчанию работает сброс кеша на уровне `__typename`:
    https://nearform.com/open-source/urql/docs/basics/document-caching/

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

    ### upd

    Добавил добавление `__typename` на всех уровнях запроса.
    1 change: 1 addition & 0 deletions request_document_cache.ts
    Original file line number Diff line number Diff line change
    @@ -12,6 +12,7 @@ namespace $ {
    }

    is_root(tn: string) {
    // отключение передергивания для корневых запросов
    return tn === 'Query' || tn === 'Mutation' || tn === 'AdminQuery' || tn === 'AdminMutation'
    }

  2. alexesDev revised this gist Apr 28, 2025. 1 changed file with 9 additions and 3 deletions.
    12 changes: 9 additions & 3 deletions request_document_cache.ts
    Original file line number Diff line number Diff line change
    @@ -1,5 +1,11 @@
    namespace $ {
    class cache extends $mol_object2 {
    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
    @@ -107,7 +113,7 @@ namespace $ {
    return out
    }

    export function $trip2g_graphql_raw_request(query: string, variables?: any) {
    export function $yourproject_graphql_raw_request(query: string, variables?: any) {
    const res = $.$mol_fetch.json('/graphql', {
    method: 'POST',
    credentials: 'include',
    @@ -116,7 +122,7 @@ namespace $ {
    }) as { data?: any; errors?: any[] }

    if (res.errors) {
    throw new $.$trip2g_graphql_error('GraphQL Error', res.errors)
    throw new $.$yourproject_graphql_error('GraphQL Error', res.errors)
    }

    const isMutation = !!query.match(/^\s+mutation/)
  3. alexesDev revised this gist Apr 28, 2025. 1 changed file with 96 additions and 19 deletions.
    115 changes: 96 additions & 19 deletions request_document_cache.ts
    Original file line number Diff line number Diff line change
    @@ -1,48 +1,125 @@
    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
    }
    }

    class cache extends $mol_object2 {
    @$mol_mem_key
    typename(key: string, val?: object) {
    return val
    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);
    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.typename(obj.__typename, reset ? obj : undefined);
    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);
    this.markTypenames(obj[key], reset)
    }
    }
    }
    }

    const cacheInstance = new cache()

    export function $yourproject_graphql_raw_request(query: string, variables?: any) {
    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 $trip2g_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 }),
    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)
    throw new $.$trip2g_graphql_error('GraphQL Error', res.errors)
    }

    const isMutation = !!query.match(/^\s+mutation/);
    const isMutation = !!query.match(/^\s+mutation/)

    cacheInstance.markTypenames(res.data, isMutation)

  4. alexesDev revised this gist Apr 26, 2025. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -32,4 +32,4 @@ mkdir input
    У urql по-умолчанию работает сброс кеша на уровне `__typename`:
    https://nearform.com/open-source/urql/docs/basics/document-caching/

    Попробовал у себя сделать так же, пока проблем не заметил. В файле `request_document_cache.ts` реализован простой кеш, который собирает `__typename` из всех query и триггерит перезагрузку когда мутация задевает связные `__typename`. Это может порождать лишние запросы, но в случае всяких админок позволяет положить болт на ручное обновление связных запросов (например если вы грузите список статей и отдельно где-то редактируется статья, после редактирования произойдет автоматическое обновление). Единственное нужно добавлять `__typename` в запросы везде, иначе этот трюк не сработает. Но typescript plugin генерит всегда типы для него, даже если бек не отдает. Можно попросить бек всегда отдавать `__typename`, даже если не просят. В теории это лишний мусор в ответе, но gzip его хорошо пожмет, можно особо не волноваться.
    Попробовал у себя сделать так же, пока проблем не заметил. В файле `request_document_cache.ts` реализован простой кеш, который собирает `__typename` из всех query и триггерит перезагрузку когда мутация задевает связные `__typename`. Это может порождать лишние запросы, но в случае всяких админок позволяет положить болт на ручное обновление связных запросов (например если вы грузите список статей и отдельно где-то редактируется статья, после редактирования произойдет автоматическое обновление). Единственное нужно добавлять `__typename` в запросы везде, иначе этот трюк не сработает.
  5. alexesDev revised this gist Apr 26, 2025. 3 changed files with 62 additions and 1 deletion.
    9 changes: 8 additions & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -25,4 +25,11 @@ mkdir input

    ### upd

    Если запросы из какого-то файла не попадают в `queries.ts` - проверь запросы. В случае ошибки graphql-codegen молча такие скипает без ошибки.
    Если запросы из какого-то файла не попадают в `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` в запросы везде, иначе этот трюк не сработает. Но typescript plugin генерит всегда типы для него, даже если бек не отдает. Можно попросить бек всегда отдавать `__typename`, даже если не просят. В теории это лишний мусор в ответе, но gzip его хорошо пожмет, можно особо не волноваться.
    3 changes: 3 additions & 0 deletions graphqlgen.js
    Original file line number Diff line number Diff line change
    @@ -1,6 +1,9 @@
    module.exports = {
    schema: 'http://localhost:8081/graphql',
    documents: [__dirname + '/**/*.ts'],
    pluckConfig: {
    globalGqlIdentifierName: ['$yourproject_graphql_request'],
    },
    generates: {
    [__dirname + '/graphql/queries.ts']: {
    plugins: [
    51 changes: 51 additions & 0 deletions request_document_cache.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,51 @@
    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?: object) {
    return val
    }

    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.typename(obj.__typename, reset ? obj : undefined);
    }

    for (const key in obj) {
    this.markTypenames(obj[key], reset);
    }
    }
    }
    }

    const cacheInstance = new cache()

    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)
    }

    const isMutation = !!query.match(/^\s+mutation/);

    cacheInstance.markTypenames(res.data, isMutation)

    return res.data
    }
    }
  6. alexesDev revised this gist Apr 26, 2025. 1 changed file with 5 additions and 1 deletion.
    6 changes: 5 additions & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -21,4 +21,8 @@ typescript plugin генерирует $fragmentName в типах и mam пыт
    ```bash
    mkdir fragment
    mkdir input
    ```
    ```

    ### upd

    Если запросы из какого-то файла не попадают в `queries.ts` - проверь запросы. В случае ошибки graphql-codegen молча такие скипает без ошибки.
  7. alexesDev revised this gist Apr 25, 2025. 1 changed file with 6 additions and 1 deletion.
    7 changes: 6 additions & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -16,4 +16,9 @@ npx graphql-codegen --config myproject/-graphqlgen.js

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

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

    ```bash
    mkdir fragment
    mkdir input
    ```
  8. alexesDev revised this gist Apr 24, 2025. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions graphqlmol.js
    Original file line number Diff line number Diff line change
    @@ -5,7 +5,7 @@ module.exports.plugin = (schema, documents, config) => {
    const operations = []

    const lines = []
    const molPrefix = config.molPrefix || 'change_in_the_config'
    const molPrefix = config.molPrefix || 'change_in_the_config'

    for (const doc of documents) {
    if (!doc.document) {
    @@ -48,4 +48,4 @@ module.exports.plugin = (schema, documents, config) => {
    )

    return lines.join('\n\n') + '\n\n}'
    }
    }
  9. alexesDev revised this gist Apr 24, 2025. 1 changed file with 0 additions and 4 deletions.
    4 changes: 0 additions & 4 deletions _example.ts
    Original file line number Diff line number Diff line change
    @@ -4,10 +4,6 @@
    return next ?? ''
    }

    email_bid() {
    return this.request_error() || ''
    }

    submit() {
    const res = $yourproject_graphql_request(
    /* GraphQL */ `
  10. alexesDev revised this gist Apr 24, 2025. 3 changed files with 10 additions and 4 deletions.
    6 changes: 5 additions & 1 deletion _readme.md
    Original file line number Diff line number Diff line change
    @@ -12,4 +12,8 @@ yourproject/
    После вызовы кодгена
    ```
    npx graphql-codegen --config myproject/-graphqlgen.js
    ```
    ```

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

    `graphqlmol.js`
    1 change: 1 addition & 0 deletions -graphqlgen.js → graphqlgen.js
    Original file line number Diff line number Diff line change
    @@ -15,6 +15,7 @@ module.exports = {
    ],
    config: {
    noExport: true,
    molPrefix: '$yourproject_graphql', // FIX ME!
    },
    },
    },
    7 changes: 4 additions & 3 deletions -graphqlmol.js → graphqlmol.js
    Original file line number Diff line number Diff line change
    @@ -5,6 +5,7 @@ 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) {
    @@ -39,12 +40,12 @@ module.exports.plugin = (schema, documents, config) => {
    passVars = `, variables`
    }

    lines.push(`export function $trip2g_graphql_request(query: '${lit}'${vars}): ${op.resultType}`)
    lines.push(`export function ${molPrefix}_request(query: '${lit}'${vars}): ${op.resultType}`)
    }

    lines.push(
    `export function $trip2g_graphql_request(query: any, variables?: any) { return $trip2g_graphql_raw_request(query, variables); }`
    `export function ${molPrefix}_request(query: any, variables?: any) { return ${molPrefix}_raw_request(query, variables); }`
    )

    return lines.join('\n\n') + '\n\n}'
    }
    }
  11. alexesDev revised this gist Apr 24, 2025. 2 changed files with 24 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions _example.ts
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    export class $trip2g_auth_email_form extends $.$trip2g_auth_email_form {
    export class $yourproject_auth_email_form extends $.$yourproject_auth_email_form {
    @$mol_mem
    request_error(next?: string): string {
    return next ?? ''
    @@ -9,7 +9,7 @@
    }

    submit() {
    const res = $trip2g_graphql_request(
    const res = $yourproject_graphql_request(
    /* GraphQL */ `
    mutation RequestEmailSignInCode($input: RequestEmailSignInCodeInput!) {
    data: requestEmailSignInCode(input: $input) {
    22 changes: 22 additions & 0 deletions request.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,22 @@
    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
    }
    }
  12. alexesDev revised this gist Apr 24, 2025. No changes.
  13. alexesDev created this gist Apr 24, 2025.
    21 changes: 21 additions & 0 deletions -graphqlgen.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    module.exports = {
    schema: 'http://localhost:8081/graphql',
    documents: [__dirname + '/**/*.ts'],
    generates: {
    [__dirname + '/graphql/queries.ts']: {
    plugins: [
    {
    add: {
    content: 'namespace $.$$ {\n\n',
    },
    },
    'typescript',
    'typescript-operations',
    __dirname + '/-graphqlmol.js',
    ],
    config: {
    noExport: true,
    },
    },
    },
    }
    50 changes: 50 additions & 0 deletions -graphqlmol.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    const { print, stripIgnoredCharacters } = require('graphql')
    const { pascalCase } = require('change-case')

    module.exports.plugin = (schema, documents, config) => {
    const operations = []

    const lines = []

    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 $trip2g_graphql_request(query: '${lit}'${vars}): ${op.resultType}`)
    }

    lines.push(
    `export function $trip2g_graphql_request(query: any, variables?: any) { return $trip2g_graphql_raw_request(query, variables); }`
    )

    return lines.join('\n\n') + '\n\n}'
    }
    48 changes: 48 additions & 0 deletions _example.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,48 @@
    export class $trip2g_auth_email_form extends $.$trip2g_auth_email_form {
    @$mol_mem
    request_error(next?: string): string {
    return next ?? ''
    }

    email_bid() {
    return this.request_error() || ''
    }

    submit() {
    const res = $trip2g_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')
    }
    }
    15 changes: 15 additions & 0 deletions _readme.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,15 @@
    Создать примерно такую структуру
    ```bash
    yourproject/
    -graphqlgen.js # Конфиг кодгена, mam пропускает все файлы с ^-
    -graphqlmol.js # Плагин под $mol_data
    graphql
    request.ts # Файл ниже, базовая функция запроса
    queries.ts # Будет создан код геном

    ```

    После вызовы кодгена
    ```
    npx graphql-codegen --config myproject/-graphqlgen.js
    ```