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
Merged
5 changes: 5 additions & 0 deletions .changeset/many-elephants-smoke.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"inngest": minor
---

INN-2754 Add support for `INNGEST_DEV` and the `isDev` option, allowing a devleoper to explicitly set either Cloud or Dev mode
17 changes: 9 additions & 8 deletions packages/inngest/etc/inngest.api.md

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

105 changes: 105 additions & 0 deletions packages/inngest/src/components/Inngest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,111 @@ const testEvent: EventPayload = {

const testEventKey = "foo-bar-baz-test";

describe("new Inngest()", () => {
describe("mode", () => {
const createTestClient = ({
env,
opts,
}: {
env?: Record<string, string>;
opts?: Omit<ConstructorParameters<typeof Inngest>[0], "id">;
} = {}): Inngest.Any => {
let ogKeys: Record<string, string | undefined> = {};

if (env) {
ogKeys = Object.keys(env).reduce<Record<string, string | undefined>>(
(acc, key) => {
acc[key] = process.env[key];
process.env[key] = env[key];
return acc;
},
{}
);
}

const inngest = new Inngest({ id: "test", ...opts });

if (env) {
Object.keys(ogKeys).forEach((key) => {
process.env[key] = ogKeys[key];
});
}

return inngest;
};

test("should default to inferred dev mode", () => {
const inngest = createTestClient();
expect(inngest["mode"].isDev).toBe(true);
expect(inngest["mode"].isExplicit).toBe(false);
});

test("`isDev: true` sets explicit dev mode", () => {
const inngest = createTestClient({ opts: { isDev: true } });
expect(inngest["mode"].isDev).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`isDev: false` sets explict cloud mode", () => {
const inngest = createTestClient({ opts: { isDev: false } });
expect(inngest["mode"].isCloud).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`INNGEST_DEV=1 sets explicit dev mode", () => {
const inngest = createTestClient({
env: { [envKeys.InngestDevMode]: "1" },
});
expect(inngest["mode"].isDev).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`INNGEST_DEV=true` sets explicit dev mode", () => {
const inngest = createTestClient({
env: { [envKeys.InngestDevMode]: "true" },
});
expect(inngest["mode"].isDev).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`INNGEST_DEV=false` sets explicit cloud mode", () => {
const inngest = createTestClient({
env: { [envKeys.InngestDevMode]: "false" },
});
expect(inngest["mode"].isCloud).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`INNGEST_DEV=0 sets explicit cloud mode", () => {
const inngest = createTestClient({
env: { [envKeys.InngestDevMode]: "0" },
});
expect(inngest["mode"].isCloud).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`isDev` overwrites `INNGEST_DEV`", () => {
const inngest = createTestClient({
env: { [envKeys.InngestDevMode]: "1" },
opts: { isDev: false },
});
expect(inngest["mode"].isDev).toBe(false);
expect(inngest["mode"].isExplicit).toBe(true);
});

test("`INNGEST_DEV=URL sets explicit dev mode", () => {
const inngest = createTestClient({
env: { [envKeys.InngestDevMode]: "http://localhost:3000" },
});
expect(inngest["mode"].isDev).toBe(true);
expect(inngest["mode"].isExplicit).toBe(true);
expect(inngest["mode"].explicitDevUrl?.href).toBe(
"http://localhost:3000/"
);
});
});
});

describe("send", () => {
describe("runtime", () => {
const originalProcessEnv = process.env;
Expand Down
62 changes: 45 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,22 @@ 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.getExplicitUrl(defaultInngestApiBaseUrl);

this.eventBaseUrl =
baseUrl ||
processEnv(envKeys.InngestEventApiBaseUrl) ||
processEnv(envKeys.InngestBaseUrl);
processEnv(envKeys.InngestBaseUrl) ||
this.mode.getExplicitUrl(defaultInngestEventBaseUrl);

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

Expand Down Expand Up @@ -407,21 +428,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 in prod mode and key is not present, fail now.
*/
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.isCloud && !this.eventKeySet()) {
throw new Error(
prettyError({
whatHappened: "Failed to send event",
Expand All @@ -432,6 +441,25 @@ export class Inngest<TOpts extends ClientOptions = ClientOptions> {
);
}

/**
* If dev mode has been inferred, 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.
*/
if (this.mode.isDev && this.mode.isInferred && !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