Skip to content

tkow/firebase-function-client-type-gen

Repository files navigation

About firebase-function-client-type-gen

Generator of client types of firebase functions derived from orginal firebase deploy definition object.

Get Started

npm i --save-dev firebase-function-client-type-gen proxyquire @types/proxyquire typescript
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 * 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.'
export const includeTest = functions
    .region('asia-northeast1')
    .runWith({
        memory: '1GB'
    })
    .https.onCall((data: RequestArgs,_): ResponseResult => {
        return {
            result: 'ok'
        }
    })
import proxyquire from 'proxyquire'
import { MOCKS } from './mock'
import { outDefinitions } from 'firebase-function-client-type-gen'
import path from 'path'
import glob from 'glob'

const firebaseFunctionEntrypoint = proxyquire('../functions/index', MOCKS)

// Get document, or throw exception on error
try {
  const sources = glob.sync(path.resolve(__dirname, '../', 'functions/endpoints/**/*.ts'))
  // The symbolConfig determine what type should be read from your https onCall definition file as its args or result.
  // If you do not specify it, default is 'RequestArgs' and 'ResponseResult'
  const result = outDefinitions(sources, firebaseFunctionEntrypoint, {
    symbolConfig: {
      args: 'RequestArgs',
      result: 'ResponseResult'
    }
  })
  console.log(result)
  console.log('done');
} catch (e) {
  console.error(e);
}

Output like

export type FunctionDefinitions = {
    "includeTest": {
        args: { id: string; };
        result: { result: "ok" | "ng"; };
    };
    "includeTestShallow": {
        args: { id: number; };
        result: { result: "success" | "failed"; };
    };
    "includeTestComposite": {
        args: Record<any, any>;
        result: { result: "success" | "failed"; };
    };
};

export const functionsMap = {
    includeTest: {
        id: "nested-namespace-includeTest",
        region: "asia-northeast1"
    },
    includeTestShallow: {
        id: "nested-includeTestShallow",
        region: "asia-northeast1"
    },
    includeTestComposite: {
        id: "includeTestComposite",
        region: "asia-northeast1"
    }
};

See tests/index.ts code more detailed usage.

Then, you can get fully type safed functions client in frontend code, like this.

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]: {
        id: string
        region?: string
    }
}

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

The template above can be generated by bin command,

npx gen-ffc ${outputpath:absolute or relative path}

and then, with your output type path, semi-automatically get type safe cliet.

// At your entrypoint file, import generated types from your generated types file.
import { FunctionDefinitions, functionsMap } from './functions-types'
import { initializeFunctions } from './outputpath'
const client = initializeFunctions<FunctionDefinitions>(functionsMap)
// Fully type-safed api call functions.
client.callSomethingReuest({...args})

Warning

  • This library for typescript firebase function users.
  • You mustn't define more than two firebase https functions in a file, they may cause bug.
  • Your args and result type must be included in the function definition file.
  • If you need to change region as basis as function, manually call const someCallable = httpsCallable(getFunction(getApp(), region), functionId) instead of initializeFunctions above.

LICENSE

MIT

About

generate httpsOnCall Types from firebase functions definitions.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published