Comparing version 1.0.0 to 1.1.0
@@ -52,2 +52,3 @@ "use strict"; | ||
url, | ||
signature: req.headers.get(consts_1.headerKeys.Signature) || undefined, | ||
}; | ||
@@ -54,0 +55,0 @@ } |
@@ -175,3 +175,3 @@ "use strict"; | ||
createFunction(nameOrOpts, trigger, fn) { | ||
return new InngestFunction_1.InngestFunction(typeof nameOrOpts === "string" ? { name: nameOrOpts } : nameOrOpts, typeof trigger === "string" ? { event: trigger } : trigger, fn); | ||
return new InngestFunction_1.InngestFunction(this, typeof nameOrOpts === "string" ? { name: nameOrOpts } : nameOrOpts, typeof trigger === "string" ? { event: trigger } : trigger, fn); | ||
} | ||
@@ -178,0 +178,0 @@ } |
@@ -0,1 +1,2 @@ | ||
import { ServerTiming } from "../helpers/ServerTiming"; | ||
import type { MaybePromise } from "../helpers/types"; | ||
@@ -173,2 +174,3 @@ import { FunctionConfig, RegisterOptions, RegisterRequest, StepRunResponse } from "../types"; | ||
private readonly fns; | ||
private allowExpiredSignatures; | ||
constructor( | ||
@@ -285,3 +287,3 @@ /** | ||
private handleAction; | ||
protected runStep(functionId: string, stepId: string | null, data: any): Promise<StepRunResponse>; | ||
protected runStep(functionId: string, stepId: string | null, data: any, timer: ServerTiming): Promise<StepRunResponse>; | ||
protected configs(url: URL): FunctionConfig[]; | ||
@@ -314,3 +316,3 @@ /** | ||
protected shouldShowLandingPage(strEnvVar: string | undefined): boolean; | ||
protected validateSignature(): boolean; | ||
protected validateSignature(sig: string | undefined, body: Record<string, any>): void; | ||
protected signResponse(): string; | ||
@@ -377,2 +379,3 @@ } | ||
url: URL; | ||
signature: string | undefined; | ||
} | { | ||
@@ -379,0 +382,0 @@ action: "bad-method"; |
@@ -10,2 +10,3 @@ "use strict"; | ||
const scalar_1 = require("../helpers/scalar"); | ||
const ServerTiming_1 = require("../helpers/ServerTiming"); | ||
const landing_1 = require("../landing"); | ||
@@ -132,2 +133,3 @@ const version_1 = require("../version"); | ||
transformRes) { | ||
var _a; | ||
/** | ||
@@ -152,2 +154,9 @@ * A property that can be set to indicate whether or not we believe we are in | ||
this.transformRes = transformRes; | ||
/** | ||
* Provide a hidden option to allow expired signatures to be accepted during | ||
* testing. | ||
*/ | ||
this.allowExpiredSignatures = Boolean( | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, prefer-rest-params | ||
(_a = arguments["3"]) === null || _a === void 0 ? void 0 : _a.__testingAllowExpiredSignatures); | ||
this.fns = functions.reduce((acc, fn) => { | ||
@@ -214,2 +223,3 @@ const id = fn.id(this.name); | ||
return async (...args) => { | ||
const timer = new ServerTiming_1.ServerTiming(); | ||
/** | ||
@@ -220,5 +230,5 @@ * We purposefully `await` the handler, as it could be either sync or | ||
// eslint-disable-next-line @typescript-eslint/await-thenable | ||
const actions = await this.handler(...args); | ||
const actionRes = await this.handleAction(actions); | ||
return this.transformRes(actionRes, ...args); | ||
const actions = await timer.wrap("handler", () => this.handler(...args)); | ||
const actionRes = await timer.wrap("action", () => this.handleAction(actions, timer)); | ||
return timer.wrap("res", () => this.transformRes(actionRes, ...args)); | ||
}; | ||
@@ -237,9 +247,14 @@ } | ||
*/ | ||
async handleAction(actions) { | ||
const headers = { "x-inngest-sdk": this.sdkHeader.join("") }; | ||
async handleAction(actions, timer) { | ||
const getHeaders = () => ({ | ||
"x-inngest-sdk": this.sdkHeader.join(""), | ||
"Server-Timing": timer.getHeader(), | ||
}); | ||
try { | ||
const runRes = await actions.run(); | ||
if (runRes) { | ||
this._isProd = runRes.isProduction; | ||
this.upsertSigningKeyFromEnv(runRes.env); | ||
const stepRes = await this.runStep(runRes.fnId, runRes.stepId, runRes.data); | ||
this.validateSignature(runRes.signature, runRes.data); | ||
const stepRes = await this.runStep(runRes.fnId, runRes.stepId, runRes.data, timer); | ||
if (stepRes.status === 500 || stepRes.status === 400) { | ||
@@ -249,3 +264,3 @@ return { | ||
body: stepRes.error || "", | ||
headers: Object.assign(Object.assign({}, headers), { "Content-Type": "application/json" }), | ||
headers: Object.assign(Object.assign({}, getHeaders()), { "Content-Type": "application/json" }), | ||
}; | ||
@@ -256,3 +271,3 @@ } | ||
body: JSON.stringify(stepRes.body), | ||
headers: Object.assign(Object.assign({}, headers), { "Content-Type": "application/json" }), | ||
headers: Object.assign(Object.assign({}, getHeaders()), { "Content-Type": "application/json" }), | ||
}; | ||
@@ -262,2 +277,3 @@ } | ||
if (viewRes) { | ||
this._isProd = viewRes.isProduction; | ||
this.upsertSigningKeyFromEnv(viewRes.env); | ||
@@ -269,3 +285,3 @@ const showLandingPage = this.shouldShowLandingPage(viewRes.env[consts_1.envKeys.LandingPage]); | ||
body: "", | ||
headers, | ||
headers: getHeaders(), | ||
}; | ||
@@ -278,3 +294,3 @@ } | ||
body: JSON.stringify(introspection), | ||
headers: Object.assign(Object.assign({}, headers), { "Content-Type": "application/json" }), | ||
headers: Object.assign(Object.assign({}, getHeaders()), { "Content-Type": "application/json" }), | ||
}; | ||
@@ -285,3 +301,3 @@ } | ||
body: landing_1.landing, | ||
headers: Object.assign(Object.assign({}, headers), { "Content-Type": "text/html; charset=utf-8" }), | ||
headers: Object.assign(Object.assign({}, getHeaders()), { "Content-Type": "text/html; charset=utf-8" }), | ||
}; | ||
@@ -291,2 +307,3 @@ } | ||
if (registerRes) { | ||
this._isProd = registerRes.isProduction; | ||
this.upsertSigningKeyFromEnv(registerRes.env); | ||
@@ -297,3 +314,3 @@ const { status, message } = await this.register(this.reqUrl(registerRes.url), registerRes.env[consts_1.envKeys.DevServerUrl], registerRes.deployId); | ||
body: JSON.stringify({ message }), | ||
headers: Object.assign(Object.assign({}, headers), { "Content-Type": "application/json" }), | ||
headers: Object.assign(Object.assign({}, getHeaders()), { "Content-Type": "application/json" }), | ||
}; | ||
@@ -306,3 +323,3 @@ } | ||
body: JSON.stringify(Object.assign({ type: "internal" }, (0, errors_1.serializeError)(err))), | ||
headers: Object.assign(Object.assign({}, headers), { "Content-Type": "application/json" }), | ||
headers: Object.assign(Object.assign({}, getHeaders()), { "Content-Type": "application/json" }), | ||
}; | ||
@@ -313,6 +330,6 @@ } | ||
body: "", | ||
headers, | ||
headers: getHeaders(), | ||
}; | ||
} | ||
async runStep(functionId, stepId, data) { | ||
async runStep(functionId, stepId, data, timer) { | ||
var _a, _b; | ||
@@ -381,4 +398,4 @@ try { | ||
*/ | ||
stepId === "step" ? null : stepId || null); | ||
if (ret[0] === "single" || ret[0] === "multi-complete") { | ||
stepId === "step" ? null : stepId || null, timer); | ||
if (ret[0] === "complete") { | ||
return { | ||
@@ -400,3 +417,3 @@ status: 200, | ||
*/ | ||
if (ret[0] === "multi-run" && ret[1].error) { | ||
if (ret[0] === "run" && ret[1].error) { | ||
throw ret[1].error; | ||
@@ -537,4 +554,6 @@ } | ||
} | ||
validateSignature() { | ||
return true; | ||
validateSignature(sig, body) { | ||
/** | ||
* Disabled until this has an integration test using raw buffers. | ||
*/ | ||
} | ||
@@ -546,2 +565,36 @@ signResponse() { | ||
exports.InngestCommHandler = InngestCommHandler; | ||
class RequestSignature { | ||
constructor(sig) { | ||
const params = new URLSearchParams(sig); | ||
this.timestamp = params.get("t") || ""; | ||
this.signature = params.get("s") || ""; | ||
if (!this.timestamp || !this.signature) { | ||
throw new Error(`Invalid ${consts_1.headerKeys.Signature} provided`); | ||
} | ||
} | ||
hasExpired(allowExpiredSignatures) { | ||
if (allowExpiredSignatures) { | ||
return false; | ||
} | ||
const delta = Date.now() - new Date(parseInt(this.timestamp) * 1000).valueOf(); | ||
return delta > 1000 * 60 * 5; | ||
} | ||
verifySignature({ body, signingKey, allowExpiredSignatures, }) { | ||
if (this.hasExpired(allowExpiredSignatures)) { | ||
throw new Error("Signature has expired"); | ||
} | ||
// Calculate the HMAC of the request body ourselves. | ||
const encoded = typeof body === "string" ? body : JSON.stringify(body); | ||
// Remove the /signkey-[test|prod]-/ prefix from our signing key to calculate the HMAC. | ||
const key = signingKey.replace(/signkey-\w+-/, ""); | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument | ||
const mac = (0, hash_js_1.hmac)(hash_js_1.sha256, key) | ||
.update(encoded) | ||
.update(this.timestamp) | ||
.digest("hex"); | ||
if (mac !== this.signature) { | ||
throw new Error("Invalid signature"); | ||
} | ||
} | ||
} | ||
//# sourceMappingURL=InngestCommHandler.js.map |
import { EventPayload, FunctionOptions, FunctionTrigger } from "../types"; | ||
import { Inngest } from "./Inngest"; | ||
/** | ||
@@ -21,3 +22,3 @@ * A stateless Inngest function, wrapping up function configuration and any | ||
*/ | ||
constructor( | ||
constructor(client: Inngest<Events>, | ||
/** | ||
@@ -24,0 +25,0 @@ * Options |
@@ -24,3 +24,3 @@ "use strict"; | ||
}; | ||
var _InngestFunction_instances, _InngestFunction_opts, _InngestFunction_trigger, _InngestFunction_fn, _InngestFunction_generateId; | ||
var _InngestFunction_instances, _InngestFunction_opts, _InngestFunction_trigger, _InngestFunction_fn, _InngestFunction_client, _InngestFunction_generateId; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
@@ -51,3 +51,3 @@ exports.InngestFunction = void 0; | ||
*/ | ||
constructor( | ||
constructor(client, | ||
/** | ||
@@ -61,2 +61,4 @@ * Options | ||
_InngestFunction_fn.set(this, void 0); | ||
_InngestFunction_client.set(this, void 0); | ||
__classPrivateFieldSet(this, _InngestFunction_client, client, "f"); | ||
__classPrivateFieldSet(this, _InngestFunction_opts, opts, "f"); | ||
@@ -148,3 +150,4 @@ __classPrivateFieldSet(this, _InngestFunction_trigger, trigger, "f"); | ||
*/ | ||
runStep) { | ||
runStep, timer) { | ||
const memoizingStop = timer.start("memoizing"); | ||
/** | ||
@@ -155,3 +158,3 @@ * Create some values to be mutated and passed to the step tools. Once the | ||
*/ | ||
const [tools, state] = (0, InngestStepTools_1.createStepTools)(); | ||
const [tools, state] = (0, InngestStepTools_1.createStepTools)(__classPrivateFieldGet(this, _InngestFunction_client, "f")); | ||
/** | ||
@@ -188,11 +191,2 @@ * Create args to pass in to our function. We blindly pass in the data and | ||
}); | ||
/** | ||
* If we haven't sychronously touched any tools yet, we can assume we're not | ||
* looking at a step function. | ||
* | ||
* Await the user function as normal. | ||
*/ | ||
if (!state.hasUsedTools) { | ||
return ["single", await userFnPromise]; | ||
} | ||
let pos = -1; | ||
@@ -215,6 +209,7 @@ do { | ||
} | ||
await (0, promises_1.resolveAfterPending)(); | ||
await timer.wrap("memoizing-ticks", promises_1.resolveAfterPending); | ||
state.reset(); | ||
pos++; | ||
} while (pos < opStack.length); | ||
memoizingStop(); | ||
if (runStep) { | ||
@@ -226,2 +221,3 @@ const userFnOp = state.allFoundOps[runStep]; | ||
} | ||
const runningStepStop = timer.start("running-step"); | ||
const result = await new Promise((resolve) => { | ||
@@ -256,5 +252,8 @@ return resolve(userFnToRun()); | ||
} | ||
}) | ||
.finally(() => { | ||
runningStepStop(); | ||
}); | ||
return [ | ||
"multi-run", | ||
"run", | ||
Object.assign(Object.assign(Object.assign({}, tickOpToOutgoing(userFnOp)), result), { op: types_1.StepOpCode.RunStep }), | ||
@@ -265,5 +264,16 @@ ]; | ||
/** | ||
* If we haven't discovered any ops, it's possible that the user's function | ||
* has completed. In this case, we should return any returned data to | ||
* Inngest as the response. | ||
* Now we're here, we've memoised any function state and we know that this | ||
* request was a discovery call to find out next steps. | ||
* | ||
* We've already given the user's function a lot of chance to register any | ||
* more ops, so we can assume that this list of discovered ops is final. | ||
* | ||
* With that in mind, if this list is empty AND we haven't previously used | ||
* any step tools, we can assume that the user's function is not one that'll | ||
* be using step tooling, so we'll just wait for it to complete and return | ||
* the result. | ||
* | ||
* An empty list while also using step tooling is a valid state when the end | ||
* of a chain of promises is reached, so we MUST also check if step tooling | ||
* has previously been used. | ||
*/ | ||
@@ -279,4 +289,4 @@ if (!discoveredOps.length) { | ||
* Inngest. Doing this will cause the function to be marked as | ||
* complete, so we should only do this if we're sure that all registered | ||
* ops have been resolved. | ||
* complete, so we should only do this if we're sure that all | ||
* registered ops have been resolved. | ||
*/ | ||
@@ -287,10 +297,10 @@ const allOpsFulfilled = Object.values(state.allFoundOps).every((op) => { | ||
if (allOpsFulfilled) { | ||
return ["multi-complete", fnRet.data]; | ||
return ["complete", fnRet.data]; | ||
} | ||
/** | ||
* If we're here, it means that the user's function has returned a value | ||
* but not all ops have been resolved. This might be intentional if they | ||
* are purposefully pushing work to the background, but also might be | ||
* unintentional and a bug in the user's code where they expected an | ||
* order to be maintained. | ||
* If we're here, it means that the user's function has returned a | ||
* value but not all ops have been resolved. This might be intentional | ||
* if they are purposefully pushing work to the background, but also | ||
* might be unintentional and a bug in the user's code where they | ||
* expected an order to be maintained. | ||
* | ||
@@ -302,8 +312,19 @@ * To be safe, we'll show a warning here to tell users that this might | ||
} | ||
else if (!state.hasUsedTools) { | ||
/** | ||
* If we're here, it means that the user's function has not returned | ||
* a value, but also has not used step tooling. This is a valid | ||
* state, indicating that the function is a single-action async | ||
* function. | ||
* | ||
* We should wait for the result and return it. | ||
*/ | ||
return ["complete", await userFnPromise]; | ||
} | ||
} | ||
return ["multi-discovery", discoveredOps]; | ||
return ["discovery", discoveredOps]; | ||
} | ||
} | ||
exports.InngestFunction = InngestFunction; | ||
_InngestFunction_opts = new WeakMap(), _InngestFunction_trigger = new WeakMap(), _InngestFunction_fn = new WeakMap(), _InngestFunction_instances = new WeakSet(), _InngestFunction_generateId = function _InngestFunction_generateId(prefix) { | ||
_InngestFunction_opts = new WeakMap(), _InngestFunction_trigger = new WeakMap(), _InngestFunction_fn = new WeakMap(), _InngestFunction_client = new WeakMap(), _InngestFunction_instances = new WeakSet(), _InngestFunction_generateId = function _InngestFunction_generateId(prefix) { | ||
return (0, strings_1.slugify)([prefix || "", __classPrivateFieldGet(this, _InngestFunction_opts, "f").name].join("-")); | ||
@@ -310,0 +331,0 @@ }; |
import { Jsonify } from "type-fest"; | ||
import type { ObjectPaths } from "../helpers/types"; | ||
import type { ObjectPaths, PartialK, SingleOrArray } from "../helpers/types"; | ||
import { EventPayload, HashedOp, StepOpCode } from "../types"; | ||
import { Inngest } from "./Inngest"; | ||
export interface TickOp extends HashedOp { | ||
@@ -18,4 +19,17 @@ fn?: (...args: any[]) => any; | ||
*/ | ||
export declare const createStepTools: <Events extends Record<string, EventPayload>, TriggeringEvent extends keyof Events>() => [{ | ||
export declare const createStepTools: <Events extends Record<string, EventPayload>, TriggeringEvent extends keyof Events>(client: Inngest<Events>) => [{ | ||
/** | ||
* Send one or many events to Inngest. Should always be used in place of | ||
* `inngest.send()` to ensure that the event send is successfully retried | ||
* and not sent multiple times due to memoisation. | ||
* | ||
* @example | ||
* ```ts | ||
* await step.sendEvent("app/user.created", { data: { id: 123 } }); | ||
* ``` | ||
* | ||
* Returns a promise that will resolve once the event has been sent. | ||
*/ | ||
sendEvent: <Event_1 extends keyof Events & string>(name: Event_1, payload: SingleOrArray<PartialK<Omit<Events[Event_1], "v" | "name">, "ts">>) => Promise<void>; | ||
/** | ||
* Wait for a particular event to be received before continuing. When the | ||
@@ -22,0 +36,0 @@ * event is received, it will be returned. |
@@ -19,3 +19,3 @@ "use strict"; | ||
*/ | ||
const createStepTools = () => { | ||
const createStepTools = (client) => { | ||
const state = { | ||
@@ -110,2 +110,22 @@ allFoundOps: {}, | ||
/** | ||
* Send one or many events to Inngest. Should always be used in place of | ||
* `inngest.send()` to ensure that the event send is successfully retried | ||
* and not sent multiple times due to memoisation. | ||
* | ||
* @example | ||
* ```ts | ||
* await step.sendEvent("app/user.created", { data: { id: 123 } }); | ||
* ``` | ||
* | ||
* Returns a promise that will resolve once the event has been sent. | ||
*/ | ||
sendEvent: createTool((name, _payload) => { | ||
return { | ||
op: types_1.StepOpCode.StepPlanned, | ||
name, | ||
}; | ||
}, (name, payload) => { | ||
return client.send(name, payload); | ||
}), | ||
/** | ||
* Wait for a particular event to be received before continuing. When the | ||
@@ -112,0 +132,0 @@ * event is received, it will be returned. |
@@ -36,2 +36,3 @@ "use strict"; | ||
isProduction, | ||
signature: req.headers.get(consts_1.headerKeys.Signature) || undefined, | ||
}; | ||
@@ -38,0 +39,0 @@ } |
import type { ServeHandler } from "./components/InngestCommHandler"; | ||
type HTTP = { | ||
headers: { | ||
host?: string; | ||
}; | ||
headers: Record<string, string>; | ||
method: string; | ||
@@ -7,0 +5,0 @@ path: string; |
@@ -55,2 +55,3 @@ "use strict"; | ||
url, | ||
signature: http.headers[consts_1.headerKeys.Signature], | ||
}; | ||
@@ -57,0 +58,0 @@ } |
@@ -30,2 +30,3 @@ "use strict"; | ||
url, | ||
signature: req.headers[consts_1.headerKeys.Signature], | ||
}; | ||
@@ -32,0 +33,0 @@ } |
@@ -18,3 +18,6 @@ export declare enum queryKeys { | ||
} | ||
export declare enum headerKeys { | ||
Signature = "x-inngest-signature" | ||
} | ||
export declare const defaultDevServerHost = "http://127.0.0.1:8288/"; | ||
//# sourceMappingURL=consts.d.ts.map |
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.defaultDevServerHost = exports.prodEnvKeys = exports.envKeys = exports.queryKeys = void 0; | ||
exports.defaultDevServerHost = exports.headerKeys = exports.prodEnvKeys = exports.envKeys = exports.queryKeys = void 0; | ||
var queryKeys; | ||
@@ -24,3 +24,7 @@ (function (queryKeys) { | ||
})(prodEnvKeys = exports.prodEnvKeys || (exports.prodEnvKeys = {})); | ||
var headerKeys; | ||
(function (headerKeys) { | ||
headerKeys["Signature"] = "x-inngest-signature"; | ||
})(headerKeys = exports.headerKeys || (exports.headerKeys = {})); | ||
exports.defaultDevServerHost = "http://127.0.0.1:8288/"; | ||
//# sourceMappingURL=consts.js.map |
@@ -23,13 +23,25 @@ "use strict"; | ||
const resolveAfterPending = () => { | ||
return new Promise((resolve) => | ||
/** | ||
* Testing found that enqueuing a single microtask would sometimes result in | ||
* the Promise being resolved before the microtask queue was drained. | ||
* This uses a brute force implementation that will continue to enqueue | ||
* microtasks 1000 times before resolving. This is to ensure that the | ||
* microtask queue is drained, even if the microtask queue is being | ||
* manipulated by other code. | ||
* | ||
* Double enqueueing does not guarantee that the queue will be empty (e.g. | ||
* if a microtask enqueues another microtask as this does), but this does | ||
* ensure that step tooling that pushes to this stack intentionally will be | ||
* correctly detected and supported. | ||
* While this still doesn't guarantee that the microtask queue is drained, | ||
* it's our best bet for giving other non-controlled promises a chance to | ||
* resolve before we continue without resorting to falling in to the next | ||
* tick. | ||
*/ | ||
queueMicrotask(() => queueMicrotask(() => resolve()))); | ||
return new Promise((resolve) => { | ||
let i = 0; | ||
const iterate = () => { | ||
queueMicrotask(() => { | ||
if (i++ > 1000) { | ||
return resolve(); | ||
} | ||
iterate(); | ||
}); | ||
}; | ||
iterate(); | ||
}); | ||
}; | ||
@@ -36,0 +48,0 @@ exports.resolveAfterPending = resolveAfterPending; |
@@ -42,2 +42,3 @@ "use strict"; | ||
url, | ||
signature: req.headers[consts_1.headerKeys.Signature], | ||
}; | ||
@@ -44,0 +45,0 @@ } |
@@ -30,2 +30,3 @@ "use strict"; | ||
stepId: (_d = (_c = query[consts_1.queryKeys.StepId]) === null || _c === void 0 ? void 0 : _c.toString()) !== null && _d !== void 0 ? _d : "", | ||
signature: (0, h3_1.getHeader)(event, consts_1.headerKeys.Signature), | ||
data: await (0, h3_1.readBody)(event), | ||
@@ -32,0 +33,0 @@ env, |
{ | ||
"name": "inngest", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Official SDK for Inngest.com", | ||
@@ -5,0 +5,0 @@ "main": "./index.js", |
@@ -10,3 +10,3 @@ <div align="center"> | ||
</p> | ||
Read the <a href="https://www.inngest.com/docs">documentation</a> and get started in minutes. | ||
Read the <a href="https://www.inngest.com/docs?ref=github-inngest-js-readme">documentation</a> and get started in minutes. | ||
<br/> | ||
@@ -42,3 +42,3 @@ <p> | ||
<a href="#contributing">Contributing</a> ยท | ||
<a href="https://www.inngest.com/docs">Documentation</a> | ||
<a href="https://www.inngest.com/docs?ref=github-inngest-js-readme">Documentation</a> | ||
</p> | ||
@@ -48,4 +48,2 @@ | ||
<img width="1489" alt="Screen Shot 2022-10-05 at 17 06 32" src="https://user-images.githubusercontent.com/306177/194185480-a942a175-0adb-42cb-9cfd-355aa03332d5.png"> | ||
## Getting started | ||
@@ -58,17 +56,26 @@ | ||
```bash | ||
npm install inngest # or yarn install inngest | ||
npm install inngest # or yarn add inngest | ||
``` | ||
<br /> | ||
### Writing functions | ||
**Writing functions**: Write serverless functions and background jobs right in your own code: | ||
Write serverless functions and background jobs right in your own code: | ||
```ts | ||
import { createFunction } from "inngest"; | ||
import { Inngest } from "inngest"; | ||
export default createFunction( | ||
"Send welcome email", | ||
"app/user.created", // Subscribe to the `app/user.created` event. | ||
({ event }) => { | ||
sendEmailTo(event.data.id, "Welcome!"); | ||
const inngest = new Inngest({ name: "My App" }); | ||
// This function will be invoked by Inngest via HTTP any time | ||
// the "app/user.signup" event is sent to to Inngest | ||
export default inngest.createFunction( | ||
{ name: "User onboarding communication" }, | ||
{ event: "app/user.signup" }, | ||
async ({ event, step }) => { | ||
await step.run("Send welcome email", async () => { | ||
await sendEmail({ | ||
email: event.data.email, | ||
template: "welcome", | ||
}); | ||
}); | ||
} | ||
@@ -78,9 +85,26 @@ ); | ||
Functions listen to events which can be triggered by API calls, webhooks, integrations, or external services. When a matching event is received, the serverless function runs automatically, with built in retries. | ||
- Functions are triggered by events which can be sent via this SDK, webhooks, integrations, or with a simple HTTP request. | ||
- When a matching event is received, Inngest invokes the function automatically, with built-in retries. | ||
<br /> | ||
### Serving your functions | ||
**Triggering functions by events:** | ||
Inngest invokes functions via HTTP, so you need to _serve_ them using an adapter for the framework of your choice. [See all frameworks here in our docs](https://www.inngest.com/docs/sdk/serve?ref=github-inngest-js-readme). Here is an example using the Next.js serve handler: | ||
```ts | ||
// /pages/api/inngest.ts | ||
import { Inngest } from "inngest"; | ||
// See the "inngest/next" adapter imported here: | ||
import { serve } from "inngest/next"; | ||
import myFunction from "../userOnboardingCOmmunication"; // see above function | ||
// You can create this in a single file and import where it's needed | ||
const inngest = new Inngest({ name: "My App" }); | ||
// Securely serve your Inngest functions for remote invocation: | ||
export default serve(inngest, [myFunction]); | ||
``` | ||
### Sending events to trigger functions | ||
```ts | ||
// Send events | ||
@@ -91,6 +115,9 @@ import { Inngest } from "inngest"; | ||
// This will run the function above automatically, in the background | ||
inngest.send("app/user.created", { data: { id: 123 } }); | ||
inngest.send("app/user.signup", { | ||
data: { email: "text@example.com", user_id: "12345" }, | ||
}); | ||
``` | ||
Events trigger any number of functions automatically, in parallel, in the background. Inngest also stores a history of all events for observability, testing, and replay. | ||
- Events can trigger one or more functions automatically, enabling you to fan-out work. | ||
- Inngest stores a history of all events for observability, testing, and replay. | ||
@@ -102,8 +129,7 @@ <br /> | ||
- **Fully serverless:** Run background jobs, scheduled functions, and build event-driven systems without any servers, state, or setup | ||
- **Deploy anywhere**: works with NextJS, Netlify, Vercel, Redwood, Express, Cloudflare, and Lambda | ||
- **Use your existing code:** write functions within your current project, zero learning required | ||
- **A complete platform**: complex functionality built in, such as **event replay**, **canary deploys**, **version management** and **git integration** | ||
- **Works with your framework**: Works with [Next.js, Redwood, Express, Cloudflare Pages, Nuxt, Fresh (Deno), and Remix](https://www.inngest.com/docs/sdk/serve?ref=github-inngest-js-readme) | ||
- **Deploy anywhere**: Keep [deploying to your existing platform](https://www.inngest.com/docs/deploy?ref=github-inngest-js-readme): Vercel, Netlify, Cloudflalre, Deno, Digital Ocean, etc. | ||
- **Use your existing code:** Write functions within your current project and repo | ||
- **Fully typed**: Event schemas, versioning, and governance out of the box | ||
- **Observable**: A full UI for managing and inspecting your functions | ||
- **Any language:** Use our CLI to write functions using any language | ||
@@ -110,0 +136,0 @@ <br /> |
@@ -51,2 +51,3 @@ "use strict"; | ||
fnId: (_a = event.queryStringParameters) === null || _a === void 0 ? void 0 : _a[consts_1.queryKeys.FnId], | ||
signature: event.headers[consts_1.headerKeys.Signature], | ||
stepId: (_b = event.queryStringParameters) === null || _b === void 0 ? void 0 : _b[consts_1.queryKeys.StepId], | ||
@@ -53,0 +54,0 @@ }; |
@@ -56,2 +56,3 @@ "use strict"; | ||
url, | ||
signature: req.headers.get(consts_1.headerKeys.Signature) || undefined, | ||
}; | ||
@@ -58,0 +59,0 @@ } |
@@ -1,2 +0,2 @@ | ||
export declare const version = "1.0.0"; | ||
export declare const version = "1.1.0"; | ||
//# sourceMappingURL=version.d.ts.map |
@@ -5,3 +5,3 @@ "use strict"; | ||
// Generated by genversion. | ||
exports.version = "1.0.0"; | ||
exports.version = "1.1.0"; | ||
//# sourceMappingURL=version.js.map |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
524224
111
5111
162