@intuitionrobotics/thunderstorm
Version:
69 lines • 3.41 kB
JavaScript
import express from "express";
import {} from "express";
import { Firebase_ExpressFunction, FirebaseFunction } from "@intuitionrobotics/firebase/backend-functions";
import { HttpServer_Class } from "./HttpServer.js";
import { terminalErrorHandler } from "./app-middleware.js";
/**
* An Express-backed HTTPS Cloud Function as a *regular module* — registered via
* `Storm.addModules(...)` like any other function, named after the module
* (no more hardcoded "api"), and instantiable as many times as needed:
*
* const Api = new ApiFunction("api", {router: rootRouter});
* const ApiTest = new ApiFunction("api_test", {router: rootRouter, options: {generation: "v2"}});
* new Storm().addModules(Api, ApiTest, ...modules).build();
*
* `api_test` deploys the exact same code as a separate function
* (`firebase deploy --only functions:api_test`) — a deployed test environment,
* or a safe gen-2 canary, without touching the real `api`.
*
* Server config (baseUrl/bodyParserLimit/cors) resolves through the module
* config store: the shared "HttpServer" slice applies to every instance, and
* the instance's own slice (config[name]) layers on top.
*
* When no ApiFunction is registered, Storm builds an implicit default named
* "api" from its legacy builder methods (setApp/setRoutes/setApiFunctionOptions)
* — registering ANY instance disables that default.
*/
export class ApiFunction extends FirebaseFunction {
providedApp;
router;
_app;
constructor(name, args = {}) {
super(name, undefined, undefined, args.options ?? {});
this.providedApp = args.app;
this.router = args.router;
}
setRoutes(router) {
this.router = router;
return this;
}
// Built lazily at getFunction() time: framework middleware (compression/CORS/
// json — via HttpServer, whose config getter layers the shared "HttpServer"
// slice under this instance's own slice) + the instance's router.
get app() {
if (this._app)
return this._app;
const app = this.providedApp ?? express();
const httpServer = new HttpServer_Class(app, this.config);
httpServer.setup();
// The function name supplies the URL prefix on Cloud Functions (the `api`
// function serves `…/api/**`), so the router always mounts at root.
if (this.router)
httpServer.mountRouter(this.router);
// Terminal error handler for raw handler()-wrapped endpoints — MUST be
// registered LAST, after the router (Express is order-sensitive). Legacy
// ServerApi endpoints catch internally and never reach it, so it is
// purely additive: only handler()'s `.catch(next)` routes here.
app.use(terminalErrorHandler());
return this._app = app;
}
getFunction = () => {
// Delegate the actual trigger wiring (v1/v2 branch, labels, lifecycle) to
// the low-level express wrapper, resolving generation lazily.
const expressFunction = new Firebase_ExpressFunction(this.app, this.getName(), undefined, undefined, { ...this.options, generation: this.generation });
this.onStart.forEach(hook => expressFunction.addOnStart(hook));
this.onDestroy.forEach(hook => expressFunction.addOnDestroy(hook));
return expressFunction.getFunction();
};
}
//# sourceMappingURL=ApiFunction.js.map