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

Add experimental @inngest/test package #688

Merged
merged 20 commits into from
Sep 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/few-brooms-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"inngest": patch
---

Expose some internal execution logic to make way for a new `@inngest/test` package
52 changes: 52 additions & 0 deletions .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,55 @@ jobs:

The last release was built and published from ${{ github.event.pull_request.head.sha }}.
edit-mode: replace

prerelease_test:
runs-on: ubuntu-latest
permissions:
contents: write
id-token: write
defaults:
run:
working-directory: packages/test
if: contains(github.event.pull_request.labels.*.name, 'prerelease/test')
steps:
- uses: actions/checkout@v3
with:
persist-credentials: false

- uses: ./.github/actions/setup-and-build
with:
install-dependencies: false
build: false

- run: pnpm install

- run: pnpm build

- name: Prerelease PR
run: node ../../scripts/release/prerelease.js
env:
TAG: pr-${{ github.event.pull_request.number }}
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NODE_ENV: test # disable npm access checks; they don't work in CI
DIST_DIR: dist

- name: Update PR with latest prerelease
uses: edumserrano/find-create-or-update-comment@v1
with:
token: ${{ secrets.CHANGESET_GITHUB_TOKEN }}
issue-number: ${{ github.event.pull_request.number }}
body-includes: "<!-- pr-prerelease-comment-test -->"
comment-author: "inngest-release-bot"
body:
| # can be a single value or you can compose text with multi-line values
<!-- pr-prerelease-comment-test -->
A user has added the <kbd>[prerelease/test](https://github.com/inngest/inngest-js/labels/prerelease%2Ftest)</kbd> label, so this PR will be published to npm with the tag `pr-${{ github.event.pull_request.number }}`. It will be updated with the latest changes as you push commits to this PR.

You can install this prerelease version with:

```sh
npm install @inngest/test@pr-${{ github.event.pull_request.number }}
```

The last release was built and published from ${{ github.event.pull_request.head.sha }}.
edit-mode: replace
2 changes: 1 addition & 1 deletion packages/inngest/src/components/InngestMiddleware.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -464,7 +464,7 @@ describe("stacking and inference", () => {
},
}),
],
}).createFunction({ id: "" }, { event: "" }, ({ step }) => {
}).createFunction({ id: "" }, { event: "" }, () => {
throw new Error("test error");
});

Expand Down
3 changes: 2 additions & 1 deletion packages/inngest/src/components/InngestStepTools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,15 @@ import { type InngestExecution } from "./execution/InngestExecution";
export interface FoundStep extends HashedOp {
hashedId: string;
fn?: (...args: unknown[]) => unknown;
rawArgs: unknown[];
fulfilled: boolean;
handled: boolean;

/**
* Returns a boolean representing whether or not the step was handled on this
* invocation.
*/
handle: () => boolean;
handle: () => Promise<boolean>;
}

export type MatchOpFn<
Expand Down
19 changes: 18 additions & 1 deletion packages/inngest/src/components/execution/InngestExecution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ export interface ExecutionResults {
}

export type ExecutionResult = {
[K in keyof ExecutionResults]: Simplify<{ type: K } & ExecutionResults[K]>;
[K in keyof ExecutionResults]: Simplify<
{
type: K;
ctx: Context.Any;
ops: Record<string, MemoizedOp>;
} & ExecutionResults[K]
>;
}[keyof ExecutionResults];

export type ExecutionResultHandler<T = ActionResponse> = (
Expand All @@ -31,6 +37,11 @@ export type ExecutionResultHandlers<T = ActionResponse> = {
};

export interface MemoizedOp extends IncomingOp {
/**
* If the step has been hit during this run, these will be the arguments
* passed to it.
*/
rawArgs?: unknown[];
fulfilled?: boolean;
seen?: boolean;
}
Expand Down Expand Up @@ -63,6 +74,12 @@ export interface InngestExecutionOptions {
timer?: ServerTiming;
isFailureHandler?: boolean;
disableImmediateExecution?: boolean;

/**
* Provide the ability to transform the context passed to the function before
* the execution starts.
*/
transformCtx?: (ctx: Readonly<Context.Any>) => Context.Any;
}

export type InngestExecutionFactory = (
Expand Down
47 changes: 43 additions & 4 deletions packages/inngest/src/components/execution/v0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ import {
type IInngestExecution,
type InngestExecutionFactory,
type InngestExecutionOptions,
type MemoizedOp,
} from "./InngestExecution";

export const createV0InngestExecution: InngestExecutionFactory = (options) => {
Expand Down Expand Up @@ -188,7 +189,12 @@ export class V0InngestExecution

const { type: _type, ...rest } = result;

return { type: "step-ran", step: { ...outgoingUserFnOp, ...rest } };
return {
type: "step-ran",
ctx: this.fnArg,
ops: this.ops,
step: { ...outgoingUserFnOp, ...rest },
};
}

if (!discoveredOps.length) {
Expand Down Expand Up @@ -235,6 +241,8 @@ export class V0InngestExecution

return {
type: "steps-found",
ctx: this.fnArg,
ops: this.ops,
steps: discoveredOps as [OutgoingOp, ...OutgoingOp[]],
};
} catch (error) {
Expand Down Expand Up @@ -312,6 +320,24 @@ export class V0InngestExecution
return state;
}

get ops(): Record<string, MemoizedOp> {
return Object.fromEntries(
Object.entries(this.state.allFoundOps).map<[string, MemoizedOp]>(
([id, op]) => [
id,
{
id: op.id,
rawArgs: op.rawArgs,
data: op.data,
error: op.error,
fulfilled: op.fulfilled,
seen: true,
},
]
)
);
}

private getUserFnToRun(): Handler.Any {
if (!this.options.isFailureHandler) {
return this.options.fn["fn"];
Expand Down Expand Up @@ -406,6 +432,7 @@ export class V0InngestExecution
this.state.tickOps[opId.id] = {
...opId,
...(opts?.fn ? { fn: () => opts.fn?.(...args) } : {}),
rawArgs: args,
resolve,
reject,
fulfilled: false,
Expand All @@ -431,7 +458,7 @@ export class V0InngestExecution
};
}

return fnArg;
return this.options.transformCtx?.(fnArg) ?? fnArg;
}

/**
Expand Down Expand Up @@ -508,14 +535,26 @@ export class V0InngestExecution

const serializedError = serializeError(error);

return { type: "function-rejected", error: serializedError, retriable };
return {
type: "function-rejected",
ctx: this.fnArg,
ops: this.ops,
error: serializedError,
retriable,
};
}

return { type: "function-resolved", data: undefinedToNull(data) };
return {
type: "function-resolved",
ctx: this.fnArg,
ops: this.ops,
data: undefinedToNull(data),
};
}
}

interface TickOp extends HashedOp {
rawArgs: unknown[];
fn?: (...args: unknown[]) => unknown;
fulfilled: boolean;
resolve: (value: MaybePromise<unknown>) => void;
Expand Down
39 changes: 34 additions & 5 deletions packages/inngest/src/components/execution/v1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
if (transformResult.type === "function-resolved") {
return {
type: "step-ran",
ctx: transformResult.ctx,
ops: transformResult.ops,
step: _internals.hashOp({
...stepResult,
data: transformResult.data,
Expand All @@ -188,6 +190,8 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
} else if (transformResult.type === "function-rejected") {
return {
type: "step-ran",
ctx: transformResult.ctx,
ops: transformResult.ops,
step: _internals.hashOp({
...stepResult,
error: transformResult.error,
Expand All @@ -205,6 +209,8 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
if (newSteps) {
return {
type: "steps-found",
ctx: this.fnArg,
ops: this.ops,
steps: newSteps,
};
}
Expand All @@ -215,7 +221,7 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
* timed out or have otherwise decided that it doesn't exist.
*/
"step-not-found": ({ step }) => {
return { type: "step-not-found", step };
return { type: "step-not-found", ctx: this.fnArg, ops: this.ops, step };
},
};
}
Expand Down Expand Up @@ -563,10 +569,21 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {

const serializedError = minifyPrettyError(serializeError(error));

return { type: "function-rejected", error: serializedError, retriable };
return {
type: "function-rejected",
ctx: this.fnArg,
ops: this.ops,
error: serializedError,
retriable,
};
}

return { type: "function-resolved", data: undefinedToNull(data) };
return {
type: "function-resolved",
ctx: this.fnArg,
ops: this.ops,
data: undefinedToNull(data),
};
}

private createExecutionState(): V1ExecutionState {
Expand Down Expand Up @@ -611,6 +628,10 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
return state;
}

get ops(): Record<string, MemoizedOp> {
return this.state.steps;
}

private createFnArg(): Context.Any {
const step = this.createStepTools();

Expand All @@ -633,7 +654,7 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
};
}

return fnArg;
return this.options.transformCtx?.(fnArg) ?? fnArg;
}

private createStepTools(): ReturnType<typeof createStepTools> {
Expand Down Expand Up @@ -839,13 +860,14 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {

const step: FoundStep = {
...opId,
rawArgs: args,
hashedId,
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
fn: opts?.fn ? () => opts.fn?.(...args) : undefined,
fulfilled: Boolean(stepState),
displayName: opId.displayName ?? opId.id,
handled: false,
handle: () => {
handle: async () => {
if (step.handled) {
return false;
}
Expand All @@ -855,6 +877,13 @@ class V1InngestExecution extends InngestExecution implements IInngestExecution {
if (stepState) {
stepState.fulfilled = true;

// For some execution scenarios such as testing, `data` and/or
// `error` may be `Promises`. This could also be the case for future
// middleware applications. For this reason, we'll make sure the
// values are fully resolved before continuing.
await stepState.data;
await stepState.error;

if (typeof stepState.data !== "undefined") {
resolve(stepState.data);
} else {
Expand Down
14 changes: 8 additions & 6 deletions packages/inngest/src/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ import {
} from "@local/components/InngestStepTools";
import {
ExecutionVersion,
IInngestExecution,
InngestExecution,
InngestExecutionOptions,
type IInngestExecution,
type InngestExecution,
type InngestExecutionOptions,
PREFERRED_EXECUTION_VERSION,
} from "@local/components/execution/InngestExecution";
import { ServerTiming } from "@local/helpers/ServerTiming";
Expand All @@ -25,7 +25,7 @@ import {
} from "@local/helpers/consts";
import { type Env } from "@local/helpers/env";
import { slugify } from "@local/helpers/strings";
import { EventPayload, type FunctionConfig } from "@local/types";
import { type EventPayload, type FunctionConfig } from "@local/types";
import { fromPartial } from "@total-typescript/shoehorn";
import fetch from "cross-fetch";
import { type Request, type Response } from "express";
Expand Down Expand Up @@ -120,7 +120,7 @@ export type StepTools = ReturnType<typeof getStepTools>;
* Given an Inngest function and the appropriate execution state, return the
* resulting data from this execution.
*/
export const runFnWithStack = (
export const runFnWithStack = async (
fn: InngestFunction.Any,
stepState: InngestExecutionOptions["stepState"],
opts?: {
Expand Down Expand Up @@ -150,7 +150,9 @@ export const runFnWithStack = (
},
});

return execution.start();
const { ctx: _ctx, ops: _ops, ...rest } = await execution.start();

return rest;
};

const inngest = createClient({ id: "test", eventKey: "event-key-123" });
Expand Down
2 changes: 2 additions & 0 deletions packages/test/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
dist
node_modules
Loading
Loading