UNPKG

inngest

Version:

Official SDK for Inngest.com. Inngest is the reliability layer for modern applications. Inngest combines durable execution, events, and queues into a zero-infra platform with built-in observability.

1 lines • 16.2 kB
{"version":3,"file":"env.cjs","names":["envKeys","version","headers: Record<string, string>","headerKeys","customFetch: typeof fetch"],"sources":["../../src/helpers/env.ts"],"sourcesContent":["// This file exists to help normalize process.env amongst the backend\n// and frontend. Many frontends (eg. Next, CRA) utilize webpack's DefinePlugin\n// along with prefixes, meaning we have to explicitly use the full `process.env.FOO`\n// string in order to read variables.\n\nimport type { Inngest } from \"../components/Inngest.ts\";\nimport type { Logger } from \"../middleware/logger.ts\";\nimport type { SupportedFrameworkName } from \"../types.ts\";\nimport { version } from \"../version.ts\";\nimport { envKeys, headerKeys } from \"./consts.ts\";\n\n/**\n * @public\n */\nexport type Env = Record<string, EnvValue>;\n\n/**\n * @public\n */\nexport type EnvValue = string | undefined;\n\n/**\n * devServerHost returns the dev server host by searching for the INNGEST_DEVSERVER_URL\n * environment variable (plus project prefixces for eg. react, such as REACT_APP_INNGEST_DEVSERVER_URL).\n *\n * If not found this returns undefined, indicating that the env var has not been set.\n *\n * @example devServerHost()\n */\nexport const devServerHost = (env: Env = allProcessEnv()): EnvValue => {\n // devServerKeys are the env keys we search for to discover the dev server\n // URL. This includes the standard key first, then includes prefixed keys\n // for use within common frameworks (eg. CRA, next).\n //\n // We have to fully write these using process.env as they're typically\n // processed using webpack's DefinePlugin, which is dumb and does a straight\n // text replacement instead of actually understanding the AST, despite webpack\n // being fully capable of understanding the AST.\n const prefixes = [\"REACT_APP_\", \"NEXT_PUBLIC_\"];\n const keys = [envKeys.InngestBaseUrl, envKeys.InngestDevMode];\n\n const values = keys.flatMap((key) => {\n return prefixes.map((prefix) => {\n return env[prefix + key];\n });\n });\n\n return values.find((v) => {\n if (!v) {\n return;\n }\n\n try {\n return Boolean(new URL(v));\n } catch {\n // no-op\n }\n\n return;\n });\n};\n\nexport type Mode = \"cloud\" | \"dev\";\n\nexport function checkModeConfiguration({\n internalLogger,\n mode,\n signingKey,\n}: {\n internalLogger: Logger;\n mode: Mode;\n signingKey: string | undefined;\n}): boolean {\n if (mode === \"cloud\" && !signingKey) {\n internalLogger.error(\n `In cloud mode but no signing key found. For local dev, set the INNGEST_DEV=1 env var. For production, set the ${envKeys.InngestSigningKey} env var`,\n );\n\n return false;\n }\n\n return true;\n}\n\nexport const normalizeUrl = (\n urlString: string,\n scheme: string = \"http://\",\n): string => {\n if (urlString === \"undefined\") {\n throw new Error(\"URL undefined\");\n }\n if (urlString.includes(\"://\")) {\n return urlString;\n }\n\n return `${scheme}${urlString}`;\n};\n\n/**\n * getEnvironmentName returns the suspected branch name for this environment by\n * searching through a set of common environment variables.\n *\n * This could be used to determine if we're on a branch deploy or not, though it\n * should be noted that we don't know if this is the default branch or not.\n */\nexport const getEnvironmentName = (env: Env = allProcessEnv()): EnvValue => {\n /**\n * Order is important; more than one of these env vars may be set, so ensure\n * that we check the most specific, most reliable env vars first.\n */\n return (\n env[envKeys.InngestEnvironment] ||\n env[envKeys.BranchName] ||\n env[envKeys.VercelBranch] ||\n env[envKeys.NetlifyBranch] ||\n env[envKeys.CloudflarePagesBranch] ||\n env[envKeys.RenderBranch] ||\n env[envKeys.RailwayBranch]\n );\n};\n\nexport const processEnv = (key: string): EnvValue => {\n return allProcessEnv()[key];\n};\n\n/**\n * The Deno environment, which is not always available.\n */\ndeclare const Deno: {\n env: { toObject: () => Env };\n};\n\n/**\n * The Netlify environment, which is not always available.\n */\ndeclare const Netlify: {\n env: { toObject: () => Env };\n};\n\n/**\n * allProcessEnv returns the current process environment variables, or an empty\n * object if they cannot be read, making sure we support environments other than\n * Node such as Deno, too.\n *\n * Using this ensures we don't dangerously access `process.env` in environments\n * where it may not be defined, such as Deno or the browser.\n */\nexport const allProcessEnv = (): Env => {\n // Node, or Node-like environments\n try {\n if (process.env) {\n return process.env;\n }\n } catch (_err) {\n // noop\n }\n\n // Deno\n try {\n const env = Deno.env.toObject();\n\n if (env) {\n return env;\n }\n } catch (_err) {\n // noop\n }\n\n // Netlify\n try {\n const env = Netlify.env.toObject();\n\n if (env) {\n return env;\n }\n } catch (_err) {\n // noop\n }\n\n return {};\n};\n\n/**\n * Generate a standardised set of headers based on input and environment\n * variables.\n *\n *\n */\nexport const inngestHeaders = (opts?: {\n /**\n * The environment variables to use instead of `process.env` or any other\n * default source. Useful for platforms where environment variables are passed\n * in alongside requests.\n */\n env?: Env;\n\n /**\n * The framework name to use in the `X-Inngest-Framework` header. This is not\n * always available, hence being optional.\n */\n framework?: string;\n\n /**\n * The environment name to use in the `X-Inngest-Env` header. This is likely\n * to be representative of the target preview environment.\n */\n inngestEnv?: string;\n\n /**\n * The Inngest client that's making the request. The client itself will\n * generate a set of headers; specifying it here will ensure that the client's\n * headers are included in the returned headers.\n */\n client?: Inngest;\n\n /**\n * The Inngest server we expect to be communicating with, used to ensure that\n * various parts of a handshake are all happening with the same type of\n * participant.\n */\n expectedServerKind?: string;\n\n /**\n * Any additional headers to include in the returned headers.\n */\n extras?: Record<string, string>;\n}): Record<string, string> => {\n const sdkVersion = `inngest-js:v${version}`;\n const headers: Record<string, string> = {\n \"Content-Type\": \"application/json\",\n \"User-Agent\": sdkVersion,\n [headerKeys.SdkVersion]: sdkVersion,\n };\n\n if (opts?.framework) {\n headers[headerKeys.Framework] = opts.framework;\n }\n\n if (opts?.expectedServerKind) {\n headers[headerKeys.InngestExpectedServerKind] = opts.expectedServerKind;\n }\n\n const env = {\n ...allProcessEnv(),\n ...opts?.env,\n };\n\n const inngestEnv = opts?.inngestEnv || getEnvironmentName(env);\n if (inngestEnv) {\n headers[headerKeys.Environment] = inngestEnv;\n }\n\n const platform = getPlatformName(env);\n if (platform) {\n headers[headerKeys.Platform] = platform;\n }\n\n return {\n ...headers,\n ...opts?.client?.[\"headers\"],\n ...opts?.extras,\n };\n};\n\n/**\n * A set of checks that, given an environment, will return `true` if the current\n * environment is running on the platform with the given name.\n */\nconst platformChecks = {\n /**\n * Vercel Edge Functions don't have access to environment variables unless\n * they are explicitly referenced in the top level code, but they do have a\n * global `EdgeRuntime` variable set that we can use to detect this.\n */\n vercel: (env) =>\n env[envKeys.IsVercel] === \"1\" || typeof EdgeRuntime === \"string\",\n netlify: (env) => env[envKeys.IsNetlify] === \"true\",\n \"cloudflare-pages\": (env) => env[envKeys.IsCloudflarePages] === \"1\",\n render: (env) => env[envKeys.IsRender] === \"true\",\n railway: (env) => Boolean(env[envKeys.RailwayEnvironment]),\n} satisfies Record<string, (env: Env) => boolean>;\n\ndeclare const EdgeRuntime: string | undefined;\n\n/**\n * A set of checks that, given an environment, will return `true` if the current\n * environment and platform supports streaming responses back to Inngest.\n *\n * Streaming capability is both framework and platform-based. Frameworks are\n * supported in serve handlers, and platforms are checked here.\n *\n * As such, this record declares which platforms we explicitly support for\n * streaming and is used by {@link platformSupportsStreaming}.\n */\nconst streamingChecks: Partial<\n Record<\n keyof typeof platformChecks,\n (framework: SupportedFrameworkName, env: Env) => boolean\n >\n> = {\n /**\n * \"Vercel supports streaming for Serverless Functions, Edge Functions, and\n * React Server Components in Next.js projects.\"\n *\n * In practice, however, there are many reports of streaming not working as\n * expected on Serverless Functions, so we resort to only allowing streaming\n * for Edge Functions here.\n *\n * See {@link https://vercel.com/docs/frameworks/nextjs#streaming}\n */\n vercel: (_framework, _env) => typeof EdgeRuntime === \"string\",\n \"cloudflare-pages\": () => true,\n};\n\nexport const getPlatformName = (env: Env) => {\n return (Object.keys(platformChecks) as (keyof typeof platformChecks)[]).find(\n (key) => {\n return platformChecks[key](env);\n },\n );\n};\n\n/**\n * Returns `true` if we believe the current environment supports streaming\n * responses back to Inngest.\n *\n * We run a check directly related to the platform we believe we're running on,\n * usually based on environment variables.\n */\nexport const platformSupportsStreaming = (\n framework: SupportedFrameworkName,\n env: Env = allProcessEnv(),\n): boolean => {\n return (\n streamingChecks[getPlatformName(env) as keyof typeof streamingChecks]?.(\n framework,\n env,\n ) ?? false\n );\n};\n\n/**\n * A unique symbol used to mark a custom fetch implementation. We wrap the\n * implementations to provide some extra control when handling errors.\n */\nconst CUSTOM_FETCH_MARKER = Symbol(\"Custom fetch implementation\");\n\n/**\n * Given a potential fetch function, return the fetch function to use based on\n * this and the environment.\n */\nexport const getFetch = (\n logger: Logger,\n givenFetch?: typeof fetch,\n): typeof fetch => {\n /**\n * If we've explicitly been given a fetch function, use that.\n */\n if (givenFetch) {\n if (CUSTOM_FETCH_MARKER in givenFetch) {\n return givenFetch;\n }\n\n /**\n * We wrap the given fetch function to provide some extra control when\n * handling errors.\n */\n const customFetch: typeof fetch = async (...args) => {\n try {\n return await givenFetch(...args);\n } catch (err) {\n /**\n * Capture warnings that are not simple fetch failures and highlight\n * them for the user.\n *\n * We also use this opportunity to log the causing error, as code higher\n * up the stack will likely abstract this.\n */\n if (\n !(err instanceof Error) ||\n !err.message?.startsWith(\"fetch failed\")\n ) {\n logger.error(\n { err },\n \"A request failed when using a custom fetch implementation; this may be a misconfiguration. Make sure that your fetch client is correctly bound to the global scope.\",\n );\n }\n\n throw err;\n }\n };\n\n /**\n * Mark the custom fetch implementation so that we can identify it later, in\n * addition to adding some runtime properties to it to make it seem as much\n * like the original fetch as possible.\n */\n Object.defineProperties(customFetch, {\n [CUSTOM_FETCH_MARKER]: {},\n name: { value: givenFetch.name },\n length: { value: givenFetch.length },\n });\n\n return customFetch;\n }\n\n /**\n * Browser or Node 18+\n */\n try {\n if (typeof globalThis !== \"undefined\" && \"fetch\" in globalThis) {\n return fetch.bind(globalThis);\n }\n } catch (_err) {\n // no-op\n }\n\n /**\n * Existing polyfilled fetch\n */\n if (typeof fetch !== \"undefined\") {\n return fetch;\n }\n\n /**\n * Environments where fetch cannot be found and must be polyfilled\n */\n return require(\"cross-fetch\") as typeof fetch;\n};\n\n/**\n * If `Response` isn't included in this environment, it's probably an earlier\n * Node env that isn't already polyfilling. This function returns either the\n * native `Response` or a polyfilled one.\n */\nexport const getResponse = (): typeof Response => {\n if (typeof Response !== \"undefined\") {\n return Response;\n }\n\n return require(\"cross-fetch\").Response;\n};\n\n/**\n * Given an unknown value, try to parse it as a `boolean`. Useful for parsing\n * environment variables that could be a selection of different values such as\n * `\"true\"`, `\"1\"`.\n *\n * If the value could not be confidently parsed as a `boolean` or was seen to be\n * `undefined`, this function returns `undefined`.\n */\nexport const parseAsBoolean = (value: unknown): boolean | undefined => {\n if (typeof value === \"boolean\") {\n return value;\n }\n\n if (typeof value === \"number\") {\n return Boolean(value);\n }\n\n if (typeof value === \"string\") {\n const trimmed = value.trim().toLowerCase();\n\n if (trimmed === \"undefined\") {\n return undefined;\n }\n\n if ([\"true\", \"1\"].includes(trimmed)) {\n return true;\n }\n\n if ([\"false\", \"0\"].includes(trimmed)) {\n return false;\n }\n }\n\n return undefined;\n};\n"],"mappings":"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"}