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 a handler for SolidStart (SolidJS) #106

Draft
wants to merge 15 commits into
base: main
Choose a base branch
from
Draft
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
44 changes: 28 additions & 16 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
fail-fast: false
max-parallel: 2
matrix:
repo:
- inngest/sdk-example-cloudflare-workers
Expand All @@ -55,6 +56,7 @@ jobs:
- inngest/sdk-example-nextjs-netlify
- inngest/sdk-example-remix-vercel
- inngest/sdk-example-nuxt-vercel
- inngest/sdk-example-solidstart-vercel
steps:
# Checkout the repo
- name: Checkout SDK
Expand All @@ -77,8 +79,8 @@ jobs:
- name: Prepare SDK for linking
run: yarn prelink
working-directory: sdk
- name: Create link to SDK
run: yarn link
- name: Pack SDK
run: yarn pack
working-directory: sdk/dist

# If we find a deno.json file in the example repo, we need to pull Deno on
Expand Down Expand Up @@ -126,13 +128,18 @@ jobs:
if test -f deno.json; then
deno run --allow-read --allow-write ../../../sdk/deno_compat/link.ts
else
yarnv=$(volta run --yarn $(cat ../../../sdk/package.json | jq -r .volta.yarn) yarn -v)
pathprefix=.

if [[ $yarnv == 3* ]]; then
touch ../../../sdk/dist/yarn.lock
yarn link ../../../sdk/dist
else
volta run --yarn $(cat ../../../sdk/package.json | jq -r .volta.yarn) yarn link inngest
if test -f .inngest.integration; then
origpath=$(pwd)
cd $(jq -r '.install_dir // "."' .inngest.integration)
pathprefix=$(realpath --relative-to=. $origpath)
fi

if test -f yarn.lock; then
yarn add $(find $pathprefix/../../../sdk/dist/ -type f -name *.tgz)
elif test -f package-lock.json; then
npm install $(find $pathprefix/../../../sdk/dist/ -type f -name *.tgz)
fi
fi
working-directory: examples/${{ matrix.repo }}
Expand Down Expand Up @@ -186,27 +193,32 @@ jobs:
- name: Run the example's dev server
run: |
if test -f yarn.lock; then
(yarn dev > dev.log 2>&1 &)
(nohup yarn dev > dev.log 2>&1 &)
elif test -f package-lock.json; then
(npm run dev > dev.log 2>&1 &)
(nohup npm run dev > dev.log 2>&1 &)
else
(deno task start > dev.log 2>&1 &)
(nohup deno task start > dev.log 2>&1 &)
fi
working-directory: examples/${{ matrix.repo }}
# Provide the example any env vars it might need
env:
PORT: 3000
DO_NOT_TRACK: 1


- name: Wait for the example to start
uses: mydea/action-wait-for-api@v1
with:
url: "http://localhost:3000/api/inngest"
url: "http://127.0.0.1:3000/api/inngest"
timeout: "60"

# Give the dev server 5 seconds to register with the example
# TODO Check logs instead of sleeping
- name: Wait 5 seconds for dev server registration
run: sleep 5
# # Give the dev server 5 seconds to register with the example
# # TODO Check logs instead of sleeping
# - name: Wait 5 seconds for dev server registration
# run: sleep 5

- if: ${{ always() }}
run: lsof -i :3000

# Run the examples test suite against the dev server
- name: Run integration test suite
Expand Down
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "inngest",
"version": "1.2.0",
"version": "1.3.0-solid.1",
"description": "Official SDK for Inngest.com",
"main": "./index.js",
"types": "./index.d.ts",
Expand Down Expand Up @@ -73,7 +73,7 @@
"@typescript-eslint/parser": "^5.47.0",
"concurrently": "^7.4.0",
"eslint": "^8.30.0",
"eslint-config-prettier": "^8.5.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"express": "^4.18.2",
"genversion": "^3.1.1",
Expand All @@ -83,8 +83,9 @@
"node-mocks-http": "^1.11.0",
"nodemon": "^2.0.20",
"np": "^7.6.1",
"prettier": "^2.7.1",
"prettier": "^2.8.4",
"shx": "^0.3.4",
"solid-start": "^0.2.21",
"ts-jest": "^29.0.3",
"type-plus": "^5.1.0",
"typescript": "^4.9.4",
Expand Down
40 changes: 40 additions & 0 deletions src/solid.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { Headers } from "cross-fetch";
import * as SolidHandler from "./solid";
import { testFramework } from "./test/helpers";

testFramework("Solid", SolidHandler, {
transformReq: (req, _res, _env, serve) => {
const headers = new Headers();
Object.entries(req.headers).forEach(([k, v]) => {
headers.set(k, v as string);
});

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
(req as any).headers = headers;

return () => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-unsafe-call
return serve[req.method]({
env: {
manifest: true,
},
request: req,
});
};
},

// eslint-disable-next-line @typescript-eslint/require-await
transformRes: async (res, ret: Response) => {
const headers: Record<string, string> = {};

ret.headers.forEach((v, k) => {
headers[k] = v;
});

return {
status: ret.status,
body: await ret.text(),
headers,
};
},
});
115 changes: 115 additions & 0 deletions src/solid.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import type { ApiHandler, Method } from "solid-start/api/types";
import {
InngestCommHandler,
ServeHandler,
} from "./components/InngestCommHandler";
import { headerKeys, queryKeys } from "./helpers/consts";
import { allProcessEnv } from "./helpers/env";

/**
* In SolidStart, serve and register any declared functions with Inngest, making
* them available to be triggered by events.
*
* SolidStart requires that you export a handler per request method, so this
* exports `GET`, `POST`, and `PUT` methods for you.
*
* @example
* ```
* import { serve } from "inngest/solid";
* import { Inngest } from "./components/Inngest";
*
* const inngest = new Inngest({ name: "My Solid App" });
*
* export const { GET, POST, PUT } = serve(inngest, [...fns]);
* ```
*
* @public
*/
export const serve: ServeHandler = (nameOrInngest, functions, opts) => {
const handler = new InngestCommHandler(
"solid",
nameOrInngest,
functions,
{
...opts,
},
(method: "GET" | "POST" | "PUT", event: Parameters<ApiHandler>[0]) => {
const env = allProcessEnv();
const isProduction =
env.VERCEL_ENV === "production" ||
env.CONTEXT === "production" ||
env.ENVIRONMENT === "production";
const scheme = env.NODE_ENV === "development" ? "http" : "https";
const url = new URL(
event.request.url,
`${scheme}://${event.request.headers.get("Host") || ""}`
);

return {
register: () => {
if (method === "PUT") {
return {
env,
isProduction,
url,
deployId: url.searchParams.get(queryKeys.DeployId) as string,
};
}
},
run: async () => {
if (method === "POST") {
return {
env,
isProduction,
url,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
data: await event.request.json(),
fnId: url.searchParams.get(queryKeys.FnId) as string,
stepId: url.searchParams.get(queryKeys.StepId) as string,
signature: event.request.headers.get(
headerKeys.Signature
) as string,
};
}
},
view: () => {
if (method === "GET") {
return {
env,
isProduction,
url,
isIntrospection: url.searchParams.has(queryKeys.Introspect),
};
}
},
};
},
({ body, headers, status }) => {
/**
* If `Response` isn't included in this environment, it's probably a Node
* env that isn't already polyfilling. In this case, we can polyfill it
* here to be safe.
*/
let Res: typeof Response;

if (typeof Response === "undefined") {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-var-requires
Res = require("cross-fetch").Response;
} else {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
Res = Response;
}

return new Res(body, {
status,
headers,
});
}
).createHandler();

return {
GET: handler.bind(null, "GET"),
POST: handler.bind(null, "POST"),
PUT: handler.bind(null, "PUT"),
} satisfies Partial<Record<Method, ApiHandler>>;
};
15 changes: 11 additions & 4 deletions src/test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,18 @@ export const testFramework = (
* If the function returns an array, the array will be used as args to the
* serve handler. If it returns void, the default request and response args
* will be used.
* Can also return a function which will be run the send the request in case
* the handler requires even more setup or local context.
*
* Returning void is useful if you need to make environment changes but
* are still fine with the default behaviour past that point.
*/
transformReq?: (
req: Request,
res: Response,
env: Record<string, string | undefined>
) => any[] | void;
env: Record<string, string | undefined>,
handler: any
) => any[] | void | (() => any);

/**
* Specify a transformer for a given response, which will be used to
Expand Down Expand Up @@ -133,8 +136,12 @@ export const testFramework = (
envToPass = { ...process.env };
}

const args = opts?.transformReq?.(req, res, envToPass) ?? [req, res];
const ret = await serveHandler(...args);
const args = opts?.transformReq?.(req, res, envToPass, serveHandler) ?? [
req,
res,
];

const ret = await (Array.isArray(args) ? serveHandler(...args) : args());

return (
opts?.transformRes?.(res, ret) ?? {
Expand Down
1 change: 1 addition & 0 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"resolveJsonModule": true,
"noUncheckedIndexedAccess": true,
"strictNullChecks": true,
"jsx": "react",
"paths": {
"inngest": ["./src"]
}
Expand Down
Loading