Skip to content

Commit

Permalink
feat!: Create runtime package and remove from SDK (#871)
Browse files Browse the repository at this point in the history
This introduces a new `@arcjet/runtime` package to the monorepo. It provides a single function that returns a [WinterCG](https://wintercg.org/) identifier for the runtime.

Since this available as a standalone package, I removed the `runtime` API from the SDKs, but the `runtime` property is also available on the `ArcjetContext`.

I finally had to give up trying to get full test coverage because Node & Jest make it very difficult to change `process.release.name`.
  • Loading branch information
blaine-arcjet authored Jun 6, 2024
1 parent 39bfcfc commit 4e9e216
Show file tree
Hide file tree
Showing 29 changed files with 730 additions and 236 deletions.
1 change: 1 addition & 0 deletions .github/.release-please-manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
"logger": "1.0.0-alpha.13",
"protocol": "1.0.0-alpha.13",
"rollup-config": "1.0.0-alpha.13",
"runtime": "1.0.0-alpha.13",
"sprintf": "1.0.0-alpha.13",
"tsconfig": "1.0.0-alpha.13"
}
4 changes: 4 additions & 0 deletions .github/release-please-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@
"component": "@arcjet/rollup-config",
"skip-github-release": true
},
"runtime": {
"component": "@arcjet/runtime",
"skip-github-release": true
},
"sprintf": {
"component": "@arcjet/sprintf",
"skip-github-release": true
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ find a specific one through the categories and descriptions below.
with information.
- [`@arcjet/duration`](./duration/README.md): Utilities for parsing duration
strings into seconds integers.
- [`@arcjet/runtime`](./runtime/README.md): Runtime detection.
- [`@arcjet/sprintf`](./sprintf/README.md): Platform-independent replacement for
`util.format`.

Expand Down
5 changes: 0 additions & 5 deletions arcjet-bun/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import core, {
ArcjetOptions,
Primitive,
Product,
Runtime,
ArcjetRequest,
ExtraProps,
RemoteClient,
Expand Down Expand Up @@ -85,7 +84,6 @@ export function createBunRemoteClient(
* make a decision about how a Bun.sh request should be handled.
*/
export interface ArcjetBun<Props extends PlainObject> {
get runtime(): Runtime;
/**
* Runs a request through the configured protections. The request is
* analyzed and then a decision made on whether to allow, deny, or challenge
Expand Down Expand Up @@ -175,9 +173,6 @@ function withClient<const Rules extends (Primitive | Product)[]>(
const ipCache = new WeakMap<Request, string>();

return Object.freeze({
get runtime() {
return aj.runtime;
},
withRule(rule: Primitive | Product) {
const client = aj.withRule(rule);
return withClient(client);
Expand Down
5 changes: 0 additions & 5 deletions arcjet-next/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import arcjet, {
ArcjetOptions,
Primitive,
Product,
Runtime,
ArcjetRequest,
ExtraProps,
RemoteClient,
Expand Down Expand Up @@ -165,7 +164,6 @@ function cookiesToString(cookies?: ArcjetNextRequest["cookies"]): string {
* make a decision about how a Next.js request should be handled.
*/
export interface ArcjetNext<Props extends PlainObject> {
get runtime(): Runtime;
/**
* Protects an API route when running under the default runtime (non-edge).
* For API routes running on the Edge Runtime, use `protect()`. The request is
Expand Down Expand Up @@ -280,9 +278,6 @@ function withClient<const Rules extends (Primitive | Product)[]>(
aj: Arcjet<ExtraProps<Rules>>,
): ArcjetNext<ExtraProps<Rules>> {
return Object.freeze({
get runtime() {
return aj.runtime;
},
withRule(rule: Primitive | Product) {
const client = aj.withRule(rule);
return withClient(client);
Expand Down
5 changes: 0 additions & 5 deletions arcjet-node/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import core, {
ArcjetOptions,
Primitive,
Product,
Runtime,
ArcjetRequest,
ExtraProps,
RemoteClient,
Expand Down Expand Up @@ -106,7 +105,6 @@ function cookiesToString(cookies: string | string[] | undefined): string {
* make a decision about how a Node.js request should be handled.
*/
export interface ArcjetNode<Props extends PlainObject> {
get runtime(): Runtime;
/**
* Runs a request through the configured protections. The request is
* analyzed and then a decision made on whether to allow, deny, or challenge
Expand Down Expand Up @@ -192,9 +190,6 @@ function withClient<const Rules extends (Primitive | Product)[]>(
aj: Arcjet<ExtraProps<Rules>>,
): ArcjetNode<ExtraProps<Rules>> {
return Object.freeze({
get runtime() {
return aj.runtime;
},
withRule(rule: Primitive | Product) {
const client = aj.withRule(rule);
return withClient(client);
Expand Down
8 changes: 2 additions & 6 deletions arcjet-sveltekit/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import core, {
ArcjetOptions,
Primitive,
Product,
Runtime,
ArcjetRequest,
ExtraProps,
RemoteClient,
Expand All @@ -16,6 +15,7 @@ import core, {
} from "arcjet";
import findIP from "@arcjet/ip";
import ArcjetHeaders from "@arcjet/headers";
import { runtime } from "@arcjet/runtime";

// Re-export all named exports from the generic SDK
export * from "arcjet";
Expand Down Expand Up @@ -60,7 +60,7 @@ type PlainObject = {
// The Vercel Adapter for SvelteKit could run on the Edge runtime, so we need to
// conditionally default the transport.
function defaultTransport(baseUrl: string) {
if (process.env["NEXT_RUNTIME"] === "edge") {
if (runtime() === "edge-light") {
return createConnectTransportWeb({
baseUrl,
interceptors: [
Expand Down Expand Up @@ -128,7 +128,6 @@ function cookiesToString(
* make a decision about how a SvelteKit request should be handled.
*/
export interface ArcjetSvelteKit<Props extends PlainObject> {
get runtime(): Runtime;
/**
* Runs a `RequestEvent` through the configured protections. The request is
* analyzed and then a decision made on whether to allow, deny, or challenge
Expand Down Expand Up @@ -195,9 +194,6 @@ function withClient<const Rules extends (Primitive | Product)[]>(
aj: Arcjet<ExtraProps<Rules>>,
): ArcjetSvelteKit<ExtraProps<Rules>> {
return Object.freeze({
get runtime() {
return aj.runtime;
},
withRule(rule: Primitive | Product) {
const client = aj.withRule(rule);
return withClient(client);
Expand Down
1 change: 1 addition & 0 deletions arcjet-sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"dependencies": {
"@arcjet/headers": "1.0.0-alpha.13",
"@arcjet/ip": "1.0.0-alpha.13",
"@arcjet/runtime": "1.0.0-alpha.13",
"@connectrpc/connect-node": "1.4.0",
"@connectrpc/connect-web": "1.4.0",
"arcjet": "1.0.0-alpha.13"
Expand Down
69 changes: 9 additions & 60 deletions arcjet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import * as analyze from "@arcjet/analyze";
import * as duration from "@arcjet/duration";
import logger from "@arcjet/logger";
import ArcjetHeaders from "@arcjet/headers";
import { runtime } from "@arcjet/runtime";

export * from "@arcjet/protocol";

Expand Down Expand Up @@ -354,7 +355,7 @@ export function createRemoteClient(
id: decision.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: runtime(),
runtime: context.runtime,
ttl: decision.ttl,
conclusion: decision.conclusion,
reason: decision.reason,
Expand Down Expand Up @@ -404,7 +405,7 @@ export function createRemoteClient(
id: response.decision?.id,
fingerprint: context.fingerprint,
path: details.path,
runtime: runtime(),
runtime: context.runtime,
ttl: decision.ttl,
});
})
Expand All @@ -418,53 +419,6 @@ export function createRemoteClient(
});
}

/**
* Represents the runtime that the client is running in. This is used to bring
* in the appropriate libraries for the runtime e.g. the WASM module.
*/
export enum Runtime {
/**
* Running in a Node.js runtime
*/
Node = "node",
/**
* Running in a Node.js runtime without WASM support e.g. Vercel serverless
* functions
* @see
* https://vercel.com/docs/concepts/functions/serverless-functions/runtimes/node-js
*/
Node_NoWASM = "node_nowasm",
/**
* Running in an Edge runtime
* @see https://edge-runtime.vercel.app/
* @see https://vercel.com/docs/concepts/functions/edge-functions/edge-runtime
*/
Edge = "edge",
}

function runtime(): Runtime {
if (typeof process.env["ARCJET_RUNTIME"] === "string") {
switch (process.env["ARCJET_RUNTIME"]) {
case "edge":
return Runtime.Edge;
case "node":
return Runtime.Node;
case "node_nowasm":
return Runtime.Node_NoWASM;
default:
throw new Error("Unknown ARCJET_RUNTIME specified!");
}
} else {
if (process.env["NEXT_RUNTIME"] === "edge") {
return Runtime.Edge;
} else if (process.env["VERCEL"] === "1") {
return Runtime.Node_NoWASM;
} else {
return Runtime.Node;
}
}
}

type TokenBucketRateLimitOptions<Characteristics extends readonly string[]> = {
mode?: ArcjetMode;
match?: string;
Expand Down Expand Up @@ -1067,7 +1021,6 @@ export interface ArcjetOptions<Rules extends [...(Primitive | Product)[]]> {
* make a decision about how a request should be handled.
*/
export interface Arcjet<Props extends PlainObject> {
get runtime(): Runtime;
/**
* Make a decision about how to handle a request. This will analyze the
* request locally where possible and call the Arcjet decision API.
Expand Down Expand Up @@ -1104,6 +1057,8 @@ export default function arcjet<
// We destructure here to make the function signature neat when viewed by consumers
const { key, rules, client } = options;

const rt = runtime();

// TODO(#207): Remove this when we can default the transport so client is not required
// It is currently optional in the options so the Next SDK can override it for the user
if (typeof client === "undefined") {
Expand Down Expand Up @@ -1159,10 +1114,10 @@ export default function arcjet<
logger.warn("generateFingerprint: ip is empty");
}
const fingerprint = await analyze.generateFingerprint(ip);
logger.debug("fingerprint (%s): %s", runtime(), fingerprint);
logger.debug("fingerprint (%s): %s", rt, fingerprint);
logger.timeEnd("fingerprint");

const context: ArcjetContext = { key, ...ctx, fingerprint };
const context: ArcjetContext = { key, ...ctx, fingerprint, runtime: rt };

if (rules.length < 1) {
// TODO(#607): Error if no rules configured after deprecation period
Expand Down Expand Up @@ -1235,7 +1190,7 @@ export default function arcjet<
conclusion: decision.conclusion,
fingerprint,
reason: existingBlockReason,
runtime: runtime(),
runtime: rt,
});

return decision;
Expand All @@ -1262,7 +1217,7 @@ export default function arcjet<
rule: rule.type,
fingerprint,
path: details.path,
runtime: runtime(),
runtime: rt,
ttl: results[idx].ttl,
conclusion: results[idx].conclusion,
reason: results[idx].reason,
Expand Down Expand Up @@ -1378,9 +1333,6 @@ export default function arcjet<
);

return Object.freeze({
get runtime() {
return runtime();
},
withRule(rule: Primitive | Product) {
return withRule(rule);
},
Expand All @@ -1394,9 +1346,6 @@ export default function arcjet<
}

return Object.freeze({
get runtime() {
return runtime();
},
withRule(rule: Primitive | Product) {
return withRule(rule);
},
Expand Down
3 changes: 2 additions & 1 deletion arcjet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"@arcjet/duration": "1.0.0-alpha.13",
"@arcjet/headers": "1.0.0-alpha.13",
"@arcjet/logger": "1.0.0-alpha.13",
"@arcjet/protocol": "1.0.0-alpha.13"
"@arcjet/protocol": "1.0.0-alpha.13",
"@arcjet/runtime": "1.0.0-alpha.13"
},
"devDependencies": {
"@arcjet/eslint-config": "1.0.0-alpha.13",
Expand Down
Loading

0 comments on commit 4e9e216

Please sign in to comment.