Nodejs Cloud Functions args and response Type Generator from backend


О чем эта статья

Введение моей библиотеки firebase-function-client-type-gen.

Резюме

Если вы используете облачные функции для firebase, вы создаете клиента, используя httpsOnCallable. Этот api принимает args и тип ответа как параметры типа typescript.

Однако синхронизация параметров запроса и ответа с бэкендом обычно не поддерживается. Данная библиотека решает эту проблему с помощью ограничений на определение функций firebase.

Пояснение

Моя библиотека извлекает тип Arg и Response, используя их псевдоним типа и имя функции, используя API компилятора Typescript, затем импортирует весь объект определения функций firebase из вашей точки входа фактического развертывания.

Учитывая, если у вас есть вложенный объект определения функции в качестве точки входа, например,

import * as functions from 'firebase-functions'

// You define two types in function definition file and they must be in a file include function declaration.
type RequestArgs = {
    id: string
}
type ResponseResult = {
    result: 'ok' | 'ng'
}

// You must export "only one const https onCall" in a file.
// If you export many httpsOnCall functions, it may happen unexpected result when mapping args and result types.'
const includeTest = functions
    .region('asia-northeast1')
    .runWith({
        memory: '1GB'
    })
    .https.onCall((data: RequestArgs, _): ResponseResult => {
        return {
            result: 'ok'
        }
    })

export const nameSpace = {
    includeTest
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Некоторые API firebase запускают код на верхнем уровне, поэтому они должны быть имитированы. Если у вас есть другой код, выполняемый в области видимости верхнего уровня, и если он вызывает ошибку во время выполнения, его также необходимо передразнить. Посмотрите на следующий пример. Я рекомендую proxyquire как инъекционный mock в ваш код и использовать его на этом примере. Mock может быть таким.

export const DUMMY_MOCKS = new Proxy<any>(
    () => DUMMY_MOCKS,
    {
        get(_, __): any {
            return DUMMY_MOCKS
        }
    }
)

export const MOCKS_BASE = {
    'firebase-functions': {
        region() {
            return DUMMY_MOCKS
        },
        config: () => {
            return {
            }
        },
        '@global': true,
        '@noCallThru': true
    },
    'firebase-admin': {
        apps: DUMMY_MOCKS,
        initializeApp: () => { return DUMMY_MOCKS },

        '@global': true,
        '@noCallThru': true
    },
}

export const MOCKS = new Proxy(MOCKS_BASE, {
    get(target, name) {
        const returnValue = target[name as keyof typeof MOCKS_BASE]
        return returnValue ?? DUMMY_MOCKS
    }
})
Вход в полноэкранный режим Выйти из полноэкранного режима

Затем найдите командный файл, генерирующий ваш код.

import proxyquire from 'proxyquire'
import { MOCKS } from './mock'
import { outDefinitions } from 'firebase-function-client-type-gen'
import path from 'path'
import glob from 'glob'
import {EOL} from 'os'

const functionDefs = proxyquire('./functions/entrypoint.ts' ,Mocks)

// Get document, or throw exception on error
try {
  const sources = glob.sync(path.resolve(__dirname, './', 'functions/**/*.ts'))
  const result = outDefinitions(sources, namedFunctions, {
    symbolConfig: {
      args: 'RequestArgs',
      result: 'ResponseResult'
    }
  })
  console.log(result)
  console.log('named functions type generated' + EOL);
} catch (e) {
  console.error(e);
}
Войти в полноэкранный режим Выйдите из полноэкранного режима

В symbolConfig можно изменить имя псевдонима вашего типа. Запустите этот код, используя среду выполнения ts, например ts-node, после чего должен появиться вывод

export type FunctionDefinitions = {
    "includeTest": {
        args: { id: string; };
        result: { result: "ok" | "ng"; };
    };
};

export const functionsMap = {
    includeTest: "nameSpace-includeTest",
};
Войти в полноэкранный режим Выход из полноэкранного режима

Вывод, конечно, может быть передан через fileWriter типа fs.
Если вы выводите его вашему приложению, то вы можете создать автоматический клиент с сохранением типов, если у каждой функции есть разные регионы.

import { getFunctions, httpsCallable, HttpsCallable } from 'firebase/functions'
import { getApp } from 'firebase/app'

type IFunctionDefnitions = {
    [key: string]: {
        args: any,
        result: any
    }
}

type HttpsCallableFuntions<FunctionDefnitions extends IFunctionDefnitions> = {
    [functionName in keyof FunctionDefnitions]: HttpsCallable<FunctionDefnitions[functionName]['args'], FunctionDefnitions[functionName]['result']>
}


type HttpsCallableFuntionIds<FunctionDefnitions> = {
    [functionName in keyof FunctionDefnitions]: string
}

export function initializeFunctions<FunctionDefnitions extends IFunctionDefnitions>(functionNameObject: HttpsCallableFuntionIds<FunctionDefnitions>, app = getApp(), region = 'us-east-1'): HttpsCallableFuntions<FunctionDefnitions> {
    const functions = getFunctions(app, region)
    const functionDefinitions = Object.entries(functionNameObject)
    return functionDefinitions.reduce((current, [functionName, functionId]) => {
        return {
            ...current,
            [functionName]: httpsCallable(functions, functionId)
        }
    }, {} as HttpsCallableFuntions<FunctionDefnitions>)
}

// At your entrypoint file, import generated types from your generated types file.
import { FunctionDefinitions, functionsMap } from './functions-types'
const client = initializeFunctions<FunctionDefinitions>(functionsMap)
// Fully type-safed api call functions.
client.callSomethingReuest({...args})
Вход в полноэкранный режим Выход из полноэкранного режима

Если вам нужно изменить регион как основу функции, вручную вызовите const someCallable = httpsCallable(getFunction(getApp(), region), functionId) вместо initializeFunctions выше.

Вот и все. Другие возможности или некоторые предостережения можно найти в Readme в моем репозитории.

Если вас заинтересовала эта библиотека, не стесняйтесь спрашивать меня.

** Обновление **

Я взял регионы из нашего клиентского генератора, поэтому больше не нужно разделять определения клиентских функций.
Кроме того, мы адаптируем простые типы псевдонимов типов или интерфейсных ссылок в наш выходной файл.

Оцените статью
Procodings.ru
Добавить комментарий