Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

INN-2754 Add support for INNGEST_DEV #488

Merged
merged 12 commits into from
Feb 23, 2024
14 changes: 8 additions & 6 deletions packages/inngest/etc/inngest.api.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

74 changes: 57 additions & 17 deletions packages/inngest/src/components/Inngest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,10 @@ import {
import { devServerAvailable, devServerUrl } from "../helpers/devserver";
import {
getFetch,
getMode,
inngestHeaders,
processEnv,
skipDevServer,
type Mode,
} from "../helpers/env";
import { fixEventKeyMissingSteps, prettyError } from "../helpers/errors";
import { stringify } from "../helpers/strings";
Expand Down Expand Up @@ -131,6 +132,18 @@ export class Inngest<TOpts extends ClientOptions = ClientOptions> {
*/
private readonly middleware: Promise<MiddlewareRegisterReturn[]>;

/**
* Whether the client is running in a production environment. This can
* sometimes be `undefined` if the client has expressed no preference or
* perhaps environment variables are only available at a later stage in the
* runtime, for example when receiving a request.
*
* An {@link InngestCommHandler} should prioritize this value over all other
* settings, but should still check for the presence of an environment
* variable if it is not set.
*/
private readonly mode: Mode;

/**
* A client used to interact with the Inngest API by sending or reacting to
* events.
Expand Down Expand Up @@ -159,6 +172,7 @@ export class Inngest<TOpts extends ClientOptions = ClientOptions> {
env,
logger = new DefaultLogger(),
middleware,
isDev,
}: TOpts) {
if (!id) {
// TODO PrettyError
Expand All @@ -167,15 +181,30 @@ export class Inngest<TOpts extends ClientOptions = ClientOptions> {

this.id = id;

this.mode = getMode({
explicitMode:
typeof isDev === "boolean" ? (isDev ? "dev" : "cloud") : undefined,
});

this.apiBaseUrl =
baseUrl ||
processEnv(envKeys.InngestApiBaseUrl) ||
processEnv(envKeys.InngestBaseUrl);
processEnv(envKeys.InngestBaseUrl) ||
(this.mode.isExplicit
? this.mode.type === "cloud"
? defaultInngestApiBaseUrl
: defaultDevServerHost
: undefined);

this.eventBaseUrl =
baseUrl ||
processEnv(envKeys.InngestEventApiBaseUrl) ||
processEnv(envKeys.InngestBaseUrl);
processEnv(envKeys.InngestBaseUrl) ||
(this.mode.isExplicit
? this.mode.type === "cloud"
? defaultInngestEventBaseUrl
: defaultDevServerHost
: undefined);

this.setEventKey(eventKey || processEnv(envKeys.InngestEventKey) || "");

Expand Down Expand Up @@ -407,21 +436,9 @@ export class Inngest<TOpts extends ClientOptions = ClientOptions> {
let url = this.sendEventUrl.href;

/**
* INNGEST_BASE_URL is used to set both dev server and prod URLs, so if a
* user has set this it means they have already chosen a URL to hit.
* If we're in prod mode and have no key, fail now.
jpwilliams marked this conversation as resolved.
Show resolved Hide resolved
*/
if (!skipDevServer()) {
if (!this.eventBaseUrl) {
const devAvailable = await devServerAvailable(
defaultDevServerHost,
this.fetch
);

if (devAvailable) {
url = devServerUrl(defaultDevServerHost, `e/${this.eventKey}`).href;
}
}
} else if (!this.eventKeySet()) {
if (this.mode.type === "cloud" && !this.eventKeySet()) {
throw new Error(
prettyError({
whatHappened: "Failed to send event",
Expand All @@ -432,6 +449,29 @@ export class Inngest<TOpts extends ClientOptions = ClientOptions> {
);
}

/**
* If we've inferred that we're in dev mode, try to hit the dev server
* first to see if it exists. If it does, use it, otherwise fall back to
* whatever server we have configured.
*
* `INNGEST_BASE_URL` is used to set both dev server and prod URLs, so if a
* user has set this it means they have already chosen a URL to hit.
*/
jpwilliams marked this conversation as resolved.
Show resolved Hide resolved
if (
this.mode.type === "dev" &&
!this.mode.isExplicit &&
!this.eventBaseUrl
) {
const devAvailable = await devServerAvailable(
defaultDevServerHost,
this.fetch
);

if (devAvailable) {
url = devServerUrl(defaultDevServerHost, `e/${this.eventKey}`).href;
}
}

const response = await this.fetch(url, {
method: "POST",
body: stringify(payloads),
Expand Down
44 changes: 22 additions & 22 deletions packages/inngest/src/components/InngestCommHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import {
allProcessEnv,
devServerHost,
getFetch,
getMode,
inngestHeaders,
isProd,
platformSupportsStreaming,
skipDevServer,
type Env,
type Mode,
} from "../helpers/env";
import { rethrowError, serializeError } from "../helpers/errors";
import {
Expand Down Expand Up @@ -252,7 +252,7 @@ export class InngestCommHandler<
*
* Should be set every time a request is received.
*/
protected _isProd = false;
protected _mode: Mode | undefined;

/**
* Whether we should attempt to use the dev server.
Expand Down Expand Up @@ -670,17 +670,20 @@ export class InngestCommHandler<
getInngestHeaders: () => Record<string, string>;
reqArgs: unknown[];
}): Promise<ActionResponse> {
this._isProd =
(await actions.isProduction?.("starting to handle request")) ??
isProd(this.env);
const assumedMode = getMode({ env: this.env, client: this.client });

/**
* If we've been explicitly passed an Inngest dev sever URL, assume that
* we shouldn't skip the dev server.
*/
this._skipDevServer = devServerHost(this.env)
? false
: this._isProd ?? skipDevServer(this.env);
if (assumedMode.isExplicit) {
this._mode = assumedMode;
} else {
const serveIsProd = await actions.isProduction?.(
"starting to handle request"
);
if (typeof serveIsProd === "boolean") {
this._mode = { type: serveIsProd ? "cloud" : "dev", isExplicit: false };
} else {
this._mode = assumedMode;
}
}

this.upsertKeysFromEnv();

Expand Down Expand Up @@ -882,8 +885,7 @@ export class InngestCommHandler<
status: 405,
body: JSON.stringify({
message: "No action found; request was likely not POST, PUT, or GET",
isProd: this._isProd,
skipDevServer: this._skipDevServer,
mode: this._mode,
}),
headers: {},
version: undefined,
Expand Down Expand Up @@ -1137,10 +1139,6 @@ export class InngestCommHandler<
return { status, message: error, modified };
}

private get isProd() {
return this._isProd;
}

/**
* Given an environment, upsert any missing keys. This is useful in
* situations where environment variables are passed directly to handlers or
Expand Down Expand Up @@ -1169,8 +1167,10 @@ export class InngestCommHandler<
}

protected validateSignature(sig: string | undefined, body: unknown) {
// Never validate signatures in development.
if (!this.isProd) {
// Never validate signatures outside of prod. Make sure we check the mode
jpwilliams marked this conversation as resolved.
Show resolved Hide resolved
// exists here instead of using nullish coalescing to confirm that the check
// has been completed.
if (this._mode && this._mode.type !== "cloud") {
return;
}

Expand Down Expand Up @@ -1408,7 +1408,7 @@ export interface ActionResponse<
* This enables us to provide accurate errors for each access without having to
* wrap every access in a try/catch.
*/
type HandlerResponseWithErrors = {
export type HandlerResponseWithErrors = {
[K in keyof HandlerResponse]: NonNullable<HandlerResponse[K]> extends (
...args: infer Args
) => infer R
Expand Down
3 changes: 1 addition & 2 deletions packages/inngest/src/helpers/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ export enum envKeys {
InngestServePath = "INNGEST_SERVE_PATH",
InngestLogLevel = "INNGEST_LOG_LEVEL",
InngestStreaming = "INNGEST_STREAMING",
InngestDevMode = "INNGEST_DEV",

BranchName = "BRANCH_NAME",

Expand Down Expand Up @@ -97,9 +98,7 @@ export enum envKeys {
* {@link https://docs.railway.app/develop/variables#railway-provided-variables}
*/
RailwayEnvironment = "RAILWAY_ENVIRONMENT",
}

export enum prodEnvKeys {
VercelEnvKey = "VERCEL_ENV",
}

Expand Down