diff --git a/.changeset/warm-steaks-approve.md b/.changeset/warm-steaks-approve.md new file mode 100644 index 000000000..8416c6bd4 --- /dev/null +++ b/.changeset/warm-steaks-approve.md @@ -0,0 +1,5 @@ +--- +"inngest": minor +--- + +Added h3 framework server handler diff --git a/packages/inngest/etc/inngest.api.md b/packages/inngest/etc/inngest.api.md index 425183932..a945366cd 100644 --- a/packages/inngest/etc/inngest.api.md +++ b/packages/inngest/etc/inngest.api.md @@ -174,7 +174,7 @@ export class InngestCommHandler, - functions: InngestFunction[], { inngestRegisterUrl, fetch, logLevel, signingKey, serveHost, servePath, streaming, name, }: RegisterOptions | undefined, + functions: InngestFunction[], options: RegisterOptions | undefined, handler: H, transformRes: TResTransform, streamTransformRes?: TStreamTransform); @@ -362,7 +362,7 @@ export type ZodEventSchemas = Record[], - { - inngestRegisterUrl, - fetch, - logLevel = "info", - signingKey, - serveHost, - servePath, - streaming, - name, - }: RegisterOptions = {}, + options: RegisterOptions = {}, /** * The `handler` is the function your framework requires to handle a @@ -396,9 +388,10 @@ export class InngestCommHandler< */ streamTransformRes?: TStreamTransform ) { - this.frameworkName = frameworkName; + this.frameworkName = + (options as InternalRegisterOptions)?.frameworkName || frameworkName; this.client = client; - this.name = name || this.client.name; + this.name = options.name || this.client.name; this.handler = handler; this.transformRes = transformRes; @@ -451,16 +444,16 @@ export class InngestCommHandler< }, {}); this.inngestRegisterUrl = new URL( - inngestRegisterUrl || "https://api.inngest.com/fn/register" + options.inngestRegisterUrl || "https://api.inngest.com/fn/register" ); - this.signingKey = signingKey; - this.serveHost = serveHost; - this.servePath = servePath; - this.logLevel = logLevel; - this.streaming = streaming ?? false; + this.signingKey = options.signingKey; + this.serveHost = options.serveHost; + this.servePath = options.servePath; + this.logLevel = options.logLevel ?? "info"; + this.streaming = options.streaming ?? false; - this.fetch = getFetch(fetch || this.client["fetch"]); + this.fetch = getFetch(options.fetch || this.client["fetch"]); } // hashedSigningKey creates a sha256 checksum of the signing key with the diff --git a/packages/inngest/src/h3.test.ts b/packages/inngest/src/h3.test.ts new file mode 100644 index 000000000..1c136b931 --- /dev/null +++ b/packages/inngest/src/h3.test.ts @@ -0,0 +1,9 @@ +import * as h3Handler from "@local/h3"; +import { createEvent } from "h3"; +import { testFramework } from "./test/helpers"; + +testFramework("h3", h3Handler, { + transformReq(req, res) { + return [createEvent(req, res)]; + }, +}); diff --git a/packages/inngest/src/h3.ts b/packages/inngest/src/h3.ts new file mode 100644 index 000000000..1ee3ab020 --- /dev/null +++ b/packages/inngest/src/h3.ts @@ -0,0 +1,76 @@ +import { + getHeader, + getQuery, + readBody, + send, + setHeaders, + type H3Event, +} from "h3"; +import { + InngestCommHandler, + type ServeHandler, +} from "./components/InngestCommHandler"; +import { headerKeys, queryKeys } from "./helpers/consts"; +import { processEnv } from "./helpers/env"; +import { type SupportedFrameworkName } from "./types"; + +export const name: SupportedFrameworkName = "h3"; + +/** + * In h3, serve and register any declared functions with Inngest, making + * them available to be triggered by events. + * + * @public + */ +export const serve: ServeHandler = (nameOrInngest, fns, opts) => { + const handler = new InngestCommHandler( + name, + nameOrInngest, + fns, + opts, + (event: H3Event) => { + const host = String(getHeader(event, "host")); + const protocol = + processEnv("NODE_ENV") === "development" ? "http" : "https"; + const url = new URL(String(event.path), `${protocol}://${host}`); + const method = event.method; + const query = getQuery(event); + + return { + url, + run: async () => { + if (method === "POST") { + return { + fnId: query[queryKeys.FnId]?.toString() ?? "", + stepId: query[queryKeys.StepId]?.toString() ?? "", + signature: getHeader(event, headerKeys.Signature), + data: await readBody(event), + }; + } + }, + register: () => { + if (method === "PUT") { + return { + deployId: query[queryKeys.DeployId]?.toString(), + }; + } + }, + view: () => { + if (method === "GET") { + return { + isIntrospection: query && queryKeys.Introspect in query, + }; + } + }, + }; + }, + (actionRes, event: H3Event) => { + const { res } = event.node; + res.statusCode = actionRes.status; + setHeaders(event, actionRes.headers); + return send(event, actionRes.body); + } + ); + + return handler.createHandler(); +}; diff --git a/packages/inngest/src/nuxt.ts b/packages/inngest/src/nuxt.ts index 21635bc85..33b6fa908 100644 --- a/packages/inngest/src/nuxt.ts +++ b/packages/inngest/src/nuxt.ts @@ -1,19 +1,9 @@ +import { type ServeHandler } from "./components/InngestCommHandler"; +import { serve as serveH3 } from "./h3"; import { - getHeader, - getMethod, - getQuery, - readBody, - send, - setHeaders, - type H3Event, -} from "h3"; -import { - InngestCommHandler, - type ServeHandler, -} from "./components/InngestCommHandler"; -import { headerKeys, queryKeys } from "./helpers/consts"; -import { processEnv } from "./helpers/env"; -import { type SupportedFrameworkName } from "./types"; + type InternalRegisterOptions, + type SupportedFrameworkName, +} from "./types"; export const name: SupportedFrameworkName = "nuxt"; @@ -23,55 +13,12 @@ export const name: SupportedFrameworkName = "nuxt"; * * @public */ -export const serve: ServeHandler = (nameOrInngest, fns, opts) => { - const handler = new InngestCommHandler( - name, - nameOrInngest, - fns, - opts, - (event: H3Event) => { - const host = String(getHeader(event, "host")); - const protocol = - processEnv("NODE_ENV") === "development" ? "http" : "https"; - const url = new URL(String(event.path), `${protocol}://${host}`); - const method = getMethod(event); - const query = getQuery(event); - - return { - url, - run: async () => { - if (method === "POST") { - return { - fnId: query[queryKeys.FnId]?.toString() ?? "", - stepId: query[queryKeys.StepId]?.toString() ?? "", - signature: getHeader(event, headerKeys.Signature), - data: await readBody(event), - }; - } - }, - register: () => { - if (method === "PUT") { - return { - deployId: query[queryKeys.DeployId]?.toString(), - }; - } - }, - view: () => { - if (method === "GET") { - return { - isIntrospection: query && queryKeys.Introspect in query, - }; - } - }, - }; - }, - (actionRes, event: H3Event) => { - const { res } = event.node; - res.statusCode = actionRes.status; - setHeaders(event, actionRes.headers); - return send(event, actionRes.body); - } - ); +export const serve: ServeHandler = (client, functions, opts) => { + const optsOverrides: InternalRegisterOptions = { + ...opts, + frameworkName: name, + }; - return handler.createHandler(); + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return serveH3(client, functions, optsOverrides); }; diff --git a/packages/inngest/src/types.ts b/packages/inngest/src/types.ts index a94138ba6..f2186aaa7 100644 --- a/packages/inngest/src/types.ts +++ b/packages/inngest/src/types.ts @@ -605,6 +605,14 @@ export interface RegisterOptions { name?: string; } +export interface InternalRegisterOptions extends RegisterOptions { + /** + * Can be used to override the framework name given to a particular serve + * handler. + */ + frameworkName?: string; +} + /** * A user-friendly method of specifying a trigger for an Inngest function. */ @@ -1019,6 +1027,7 @@ export type SupportedFrameworkName = | "aws-lambda" | "nextjs" | "nuxt" + | "h3" | "redwoodjs" | "remix" | "deno/fresh" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 66e09612b..d68a231af 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -36,8 +36,8 @@ importers: specifier: ^4.0.0 version: 4.0.0 h3: - specifier: ^1.0.2 - version: 1.0.2 + specifier: ^1.8.1 + version: 1.8.1 hash.js: specifier: ^1.1.7 version: 1.1.7 @@ -2641,6 +2641,11 @@ packages: yargs: 17.7.1 dev: true + /consola@3.2.3: + resolution: {integrity: sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==} + engines: {node: ^14.18.0 || >=16.10.0} + dev: false + /content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -2671,8 +2676,8 @@ packages: resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} dev: true - /cookie-es@0.5.0: - resolution: {integrity: sha512-RyZrFi6PNpBFbIaQjXDlFIhFVqV42QeKSZX1yQIl6ihImq6vcHNGMtqQ/QzY3RMPuYSkvsRwtnt5M9NeYxKt0g==} + /cookie-es@1.0.0: + resolution: {integrity: sha512-mWYvfOLrfEc996hlKcdABeIiPHUPC6DM2QYZdGGOvhOTbA3tjm2eBwqlJpoFdjC89NI4Qt6h0Pu06Mp+1Pj5OQ==} dev: false /cookie-signature@1.0.6: @@ -2816,6 +2821,10 @@ packages: object-keys: 1.1.1 dev: true + /defu@6.1.2: + resolution: {integrity: sha512-+uO4+qr7msjNNWKYPHqN/3+Dx3NFkmIzayk2L1MyZQlvgZb/J1A0fo410dpKrN2SnqFjt8n4JL8fDJE0wIgjFQ==} + dev: false + /delayed-stream@1.0.0: resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} engines: {node: '>=0.4.0'} @@ -2831,8 +2840,8 @@ packages: engines: {node: '>= 0.8'} dev: true - /destr@1.2.2: - resolution: {integrity: sha512-lrbCJwD9saUQrqUfXvl6qoM+QN3W7tLV5pAOs+OqOmopCCz/JkE05MHedJR1xfk4IAnZuJXPVuN5+7jNA2ZCiA==} + /destr@2.0.1: + resolution: {integrity: sha512-M1Ob1zPSIvlARiJUkKqvAZ3VAqQY6Jcuth/pBKQ2b1dX/Qx0OnJ8Vux6J2H5PTMQeRzWrrbTu70VxBfv/OPDJA==} dev: false /destroy@1.2.0: @@ -3941,13 +3950,17 @@ packages: /grapheme-splitter@1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} - /h3@1.0.2: - resolution: {integrity: sha512-25QqjQMz8pX1NI2rZ/ziNT9B8Aog7jmu2a0o8Qm9kKoH3zOhE+2icVs069h6DEp0g1Dst1+zKfRdRYcK0MogJA==} - dependencies: - cookie-es: 0.5.0 - destr: 1.2.2 - radix3: 1.0.0 - ufo: 1.0.1 + /h3@1.8.1: + resolution: {integrity: sha512-m5rFuu+5bpwBBHqqS0zexjK+Q8dhtFRvO9JXQG0RvSPL6QrIT6vv42vuBM22SLOgGMoZYsHk0y7VPidt9s+nkw==} + dependencies: + cookie-es: 1.0.0 + defu: 6.1.2 + destr: 2.0.1 + iron-webcrypto: 0.8.2 + radix3: 1.1.0 + ufo: 1.3.0 + uncrypto: 0.1.3 + unenv: 1.7.4 dev: false /hard-rejection@2.1.0: @@ -4130,6 +4143,10 @@ packages: engines: {node: '>= 0.10'} dev: true + /iron-webcrypto@0.8.2: + resolution: {integrity: sha512-jGiwmpgTuF19Vt4hn3+AzaVFGpVZt7A1ysd5ivFel2r4aNVFwqaYa6aU6qsF1PM7b+WFivZHz3nipwUOXaOnHg==} + dev: false + /is-array-buffer@3.0.1: resolution: {integrity: sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==} dependencies: @@ -5086,6 +5103,12 @@ packages: hasBin: true dev: true + /mime@3.0.0: + resolution: {integrity: sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==} + engines: {node: '>=10.0.0'} + hasBin: true + dev: false + /mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -5224,6 +5247,10 @@ packages: - supports-color dev: true + /node-fetch-native@1.4.0: + resolution: {integrity: sha512-F5kfEj95kX8tkDhUCYdV8dg3/8Olx/94zB8+ZNthFs6Bz31UpUi8Xh40TN3thLwXgrwXry1pEg9lJ++tLWTcqA==} + dev: false + /node-fetch@2.6.9: resolution: {integrity: sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==} engines: {node: 4.x || >=6.0.0} @@ -5515,6 +5542,10 @@ packages: engines: {node: '>=8'} dev: true + /pathe@1.1.1: + resolution: {integrity: sha512-d+RQGp0MAYTIaDBIMmOfMwz3E+LOZnxx1HZd5R18mmCZY0QBlK0LDZfPc8FW8Ed2DlvsuE6PRjroDY+wg4+j/Q==} + dev: false + /picocolors@1.0.0: resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} dev: true @@ -5700,8 +5731,8 @@ packages: engines: {node: '>=8'} dev: true - /radix3@1.0.0: - resolution: {integrity: sha512-6n3AEXth91ASapMVKiEh2wrbFJmI+NBilrWE0AbiGgfm0xet0QXC8+a3K19r1UVYjUjctUgB053c3V/J6V0kCQ==} + /radix3@1.1.0: + resolution: {integrity: sha512-pNsHDxbGORSvuSScqNJ+3Km6QAVqk8CfsCBIEoDgpqLrkD2f3QM4I7d1ozJJ172OmIcoUcerZaNWqtLkRXTV3A==} dev: false /range-parser@1.2.1: @@ -6658,8 +6689,8 @@ packages: hasBin: true dev: true - /ufo@1.0.1: - resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + /ufo@1.3.0: + resolution: {integrity: sha512-bRn3CsoojyNStCZe0BG0Mt4Nr/4KF+rhFlnNXybgqt5pXHNFRlqinSoQaTrGyzE4X8aHplSb+TorH+COin9Yxw==} dev: false /ulid@2.3.0: @@ -6676,10 +6707,24 @@ packages: which-boxed-primitive: 1.0.2 dev: true + /uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + dev: false + /undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} dev: true + /unenv@1.7.4: + resolution: {integrity: sha512-fjYsXYi30It0YCQYqLOcT6fHfMXsBr2hw9XC7ycf8rTG7Xxpe3ZssiqUnD0khrjiZEmkBXWLwm42yCSCH46fMw==} + dependencies: + consola: 3.2.3 + defu: 6.1.2 + mime: 3.0.0 + node-fetch-native: 1.4.0 + pathe: 1.1.1 + dev: false + /universalify@0.1.2: resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} engines: {node: '>= 4.0.0'}