@arcjet/node 1.0.0-beta.9 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +43 -71
  2. package/index.d.ts +147 -35
  3. package/index.js +62 -77
  4. package/package.json +20 -18
package/README.md CHANGED
@@ -37,111 +37,83 @@ Visit the [quick start guide][quick-start] to get started.
37
37
  Try an Arcjet protected app live at [https://example.arcjet.com][example-url]
38
38
  ([source code][example-source]).
39
39
 
40
- ## Installation
40
+ ## What is this?
41
41
 
42
- ```shell
43
- npm install -S @arcjet/node
44
- ```
45
-
46
- ## Rate limit example
47
-
48
- The example below applies a token bucket rate limit rule to a route where we
49
- identify the user based on their ID e.g. if they are logged in. The bucket is
50
- configured with a maximum capacity of 10 tokens and refills by 5 tokens every 10
51
- seconds. Each request consumes 5 tokens.
42
+ This is our adapter to integrate Arcjet into Node.js.
43
+ Arcjet helps you secure your Node server.
44
+ This package exists so that we can provide the best possible experience to
45
+ Node users.
52
46
 
53
- Bot detection is also enabled to block requests from known bots.
47
+ ## When should I use this?
54
48
 
55
- ```ts
56
- import arcjet, { tokenBucket, detectBot } from "@arcjet/node";
57
- import http from "node:http";
49
+ You can use this if you are using Node.js.
50
+ See our [_Get started_ guide][arcjet-get-started] for other supported
51
+ frameworks and runtimes.
58
52
 
59
- const aj = arcjet({
60
- key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
61
- characteristics: ["userId"], // track requests by a custom user ID
62
- rules: [
63
- // Create a token bucket rate limit. Other algorithms are supported.
64
- tokenBucket({
65
- mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
66
- refillRate: 5, // refill 5 tokens per interval
67
- interval: 10, // refill every 10 seconds
68
- capacity: 10, // bucket maximum capacity of 10 tokens
69
- }),
70
- detectBot({
71
- mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
72
- // configured with a list of bots to allow from
73
- // https://arcjet.com/bot-list
74
- allow: [], // "allow none" will block all detected bots
75
- }),
76
- ],
77
- });
53
+ ## Install
78
54
 
79
- const server = http.createServer(async function (
80
- req: http.IncomingMessage,
81
- res: http.ServerResponse,
82
- ) {
83
- const userId = "user123"; // Replace with your authenticated user ID
84
- const decision = await aj.protect(req, { userId, requested: 5 }); // Deduct 5 tokens from the bucket
85
- console.log("Arcjet decision", decision);
55
+ This package is ESM only.
56
+ Install with npm in Node.js:
86
57
 
87
- if (decision.isDenied()) {
88
- res.writeHead(403, { "Content-Type": "application/json" });
89
- res.end(JSON.stringify({ error: "Forbidden" }));
90
- } else {
91
- res.writeHead(200, { "Content-Type": "application/json" });
92
- res.end(JSON.stringify({ message: "Hello world" }));
93
- }
94
- });
95
-
96
- server.listen(8000);
58
+ ```sh
59
+ npm install @arcjet/node
97
60
  ```
98
61
 
99
- ## Shield example
100
-
101
- [Arcjet Shield][shield-concepts-docs] protects your application against common
102
- attacks, including the OWASP Top 10. You can run Shield on every request with
103
- negligible performance impact.
62
+ ## Use
104
63
 
105
64
  ```ts
106
- import arcjet, { shield } from "@arcjet/node";
107
65
  import http from "node:http";
66
+ import arcjet, { shield } from "@arcjet/node";
67
+
68
+ // Get your Arcjet key at <https://app.arcjet.com>.
69
+ // Set it as an environment variable instead of hard coding it.
70
+ const arcjetKey = process.env.ARCJET_KEY;
71
+
72
+ if (!arcjetKey) {
73
+ throw new Error("Cannot find `ARCJET_KEY` environment variable");
74
+ }
108
75
 
109
76
  const aj = arcjet({
110
- key: process.env.ARCJET_KEY!, // Get your site key from https://app.arcjet.com
77
+ key: arcjetKey,
111
78
  rules: [
112
- shield({
113
- mode: "LIVE", // will block requests. Use "DRY_RUN" to log only
114
- }),
79
+ // Shield protects your app from common attacks.
80
+ // Use `DRY_RUN` instead of `LIVE` to only log.
81
+ shield({ mode: "LIVE" }),
115
82
  ],
116
83
  });
117
84
 
118
85
  const server = http.createServer(async function (
119
- req: http.IncomingMessage,
120
- res: http.ServerResponse,
86
+ request: http.IncomingMessage,
87
+ response: http.ServerResponse,
121
88
  ) {
122
- const decision = await aj.protect(req);
89
+ const decision = await aj.protect(request);
123
90
 
124
91
  if (decision.isDenied()) {
125
- res.writeHead(403, { "Content-Type": "application/json" });
126
- res.end(JSON.stringify({ error: "Forbidden" }));
127
- } else {
128
- res.writeHead(200, { "Content-Type": "application/json" });
129
- res.end(JSON.stringify({ message: "Hello world" }));
92
+ response.writeHead(403, { "Content-Type": "application/json" });
93
+ response.end(JSON.stringify({ message: "Forbidden" }));
94
+ return;
130
95
  }
96
+
97
+ response.writeHead(200, { "Content-Type": "application/json" });
98
+ response.end(JSON.stringify({ message: "Hello world" }));
131
99
  });
132
100
 
133
101
  server.listen(8000);
134
102
  ```
135
103
 
104
+ For more on how to configure Arcjet with Node.js and how to protect Node,
105
+ see the [Arcjet Node.js SDK reference][arcjet-reference-node] on our website.
106
+
136
107
  ## License
137
108
 
138
- Licensed under the [Apache License, Version 2.0][apache-license].
109
+ [Apache License, Version 2.0][apache-license] © [Arcjet Labs, Inc.][arcjet]
139
110
 
111
+ [arcjet-get-started]: https://docs.arcjet.com/get-started
112
+ [arcjet-reference-node]: https://docs.arcjet.com/reference/nodejs
140
113
  [arcjet]: https://arcjet.com
141
114
  [node-js]: https://nodejs.org/
142
115
  [alt-sdk]: https://www.npmjs.com/package/@arcjet/next
143
116
  [example-url]: https://example.arcjet.com
144
117
  [quick-start]: https://docs.arcjet.com/get-started/nodejs
145
118
  [example-source]: https://github.com/arcjet/arcjet-js-example
146
- [shield-concepts-docs]: https://docs.arcjet.com/shield/concepts
147
119
  [apache-license]: http://www.apache.org/licenses/LICENSE-2.0
package/index.d.ts CHANGED
@@ -4,72 +4,184 @@ type Simplify<T> = {
4
4
  [KeyType in keyof T]: T[KeyType];
5
5
  } & {};
6
6
  declare const emptyObjectSymbol: unique symbol;
7
- type WithoutCustomProps = {
8
- [emptyObjectSymbol]?: never;
9
- };
10
7
  type PlainObject = {
11
8
  [key: string]: unknown;
12
9
  };
10
+ /**
11
+ * Dynamically generate whether zero or one `properties` object must or can be passed.
12
+ */
13
+ type MaybeProperties<T> = {
14
+ [P in keyof T]?: T[P];
15
+ } extends T ? T extends {
16
+ [emptyObjectSymbol]?: never;
17
+ } ? [
18
+ ] : [
19
+ properties?: T
20
+ ] : [
21
+ properties: T
22
+ ];
23
+ /**
24
+ * Configuration for {@linkcode createRemoteClient}.
25
+ */
13
26
  export type RemoteClientOptions = {
27
+ /**
28
+ * Base URI for HTTP requests to Decide API (optional).
29
+ *
30
+ * Defaults to the environment variable `ARCJET_BASE_URL` (if that value
31
+ * is known and allowed) and the standard production API otherwise.
32
+ */
14
33
  baseUrl?: string;
34
+ /**
35
+ * Timeout in milliseconds for the Decide API (optional).
36
+ *
37
+ * Defaults to `500` in production and `1000` in development.
38
+ */
15
39
  timeout?: number;
16
40
  };
41
+ /**
42
+ * Create a remote client.
43
+ *
44
+ * @param options
45
+ * Configuration (optional).
46
+ * @returns
47
+ * Client.
48
+ */
17
49
  export declare function createRemoteClient(options?: RemoteClientOptions): import("@arcjet/protocol/client.js").Client;
18
50
  type EventHandlerLike = (event: string, listener: (...args: any[]) => void) => void;
51
+ /**
52
+ * Request for the Node.js integration of Arcjet.
53
+ *
54
+ * This is the minimum interface similar to `http.IncomingMessage`.
55
+ */
19
56
  export interface ArcjetNodeRequest {
20
- headers?: Record<string, string | string[] | undefined>;
57
+ /**
58
+ * Headers of the request.
59
+ */
60
+ headers?: Record<string, string | string[] | undefined> | undefined;
61
+ /**
62
+ * `net.Socket` object associated with the connection.
63
+ *
64
+ * See <https://nodejs.org/api/http.html#messagesocket>.
65
+ */
21
66
  socket?: Partial<{
22
- remoteAddress: string;
67
+ remoteAddress?: string | undefined;
23
68
  encrypted: boolean;
24
- }>;
25
- method?: string;
26
- httpVersion?: string;
27
- url?: string;
69
+ }> | undefined;
70
+ /**
71
+ * HTTP method of the request.
72
+ */
73
+ method?: string | undefined;
74
+ /**
75
+ * HTTP version sent by the client.
76
+ *
77
+ * See <https://nodejs.org/api/http.html#messagehttpversion>.
78
+ */
79
+ httpVersion?: string | undefined;
80
+ /**
81
+ * URL.
82
+ */
83
+ url?: string | undefined;
84
+ /**
85
+ * Request body.
86
+ */
28
87
  body?: unknown;
29
- on?: EventHandlerLike;
30
- removeListener?: EventHandlerLike;
31
- readable?: boolean;
88
+ /**
89
+ * Add event handlers.
90
+ *
91
+ * This field is available through `stream.Readable` from `EventEmitter`.
92
+ *
93
+ * See <https://nodejs.org/api/events.html#emitteroneventname-listener>.
94
+ */
95
+ on?: EventHandlerLike | undefined;
96
+ /**
97
+ * Remove event handlers.
98
+ *
99
+ * This field is available through `stream.Readable` from `EventEmitter`.
100
+ *
101
+ * See <https://nodejs.org/api/events.html#emitterremovelistenereventname-listener>.
102
+ */
103
+ removeListener?: EventHandlerLike | undefined;
104
+ /**
105
+ * Whether the readable stream is readable.
106
+ *
107
+ * This field is available from `stream.Readable`.
108
+ *
109
+ * See <https://nodejs.org/api/stream.html#readablereadable>.
110
+ */
111
+ readable?: boolean | undefined;
32
112
  }
33
113
  /**
34
- * The options used to configure an {@link ArcjetNode} client.
114
+ * Configuration for the Node.js integration of Arcjet.
115
+ *
116
+ * @template Rules
117
+ * List of rules.
118
+ * @template Characteristics
119
+ * Characteristics to track a user by.
35
120
  */
36
121
  export type ArcjetOptions<Rules extends [...Array<Primitive | Product>], Characteristics extends readonly string[]> = Simplify<CoreOptions<Rules, Characteristics> & {
37
122
  /**
38
- * One or more IP Address of trusted proxies in front of the application.
39
- * These addresses will be excluded when Arcjet detects a public IP address.
123
+ * IP addresses and CIDR ranges of trusted load balancers and proxies
124
+ * (optional, example: `["100.100.100.100", "100.100.100.0/24"]`).
40
125
  */
41
126
  proxies?: Array<string>;
42
127
  }>;
43
128
  /**
44
- * The ArcjetNode client provides a public `protect()` method to
45
- * make a decision about how a Node.js request should be handled.
129
+ * Instance of the Node.js integration of Arcjet.
130
+ *
131
+ * Primarily has a `protect()` method to make a decision about how a Node request
132
+ * should be handled.
133
+ *
134
+ * @template Props
135
+ * Configuration.
46
136
  */
47
137
  export interface ArcjetNode<Props extends PlainObject> {
48
138
  /**
49
- * Runs a request through the configured protections. The request is
50
- * analyzed and then a decision made on whether to allow, deny, or challenge
51
- * the request.
139
+ * Make a decision about how to handle a request.
52
140
  *
53
- * @param req - An `IncomingMessage` provided to the request handler.
54
- * @param props - Additonal properties required for running rules against a request.
55
- * @returns An {@link ArcjetDecision} indicating Arcjet's decision about the request.
141
+ * This will analyze the request locally where possible and otherwise call
142
+ * the Arcjet decision API.
143
+ *
144
+ * @param request
145
+ * Details about the {@linkcode ArcjetNodeRequest} that Arcjet needs to make a
146
+ * decision.
147
+ * @param props
148
+ * Additional properties required for running rules against a request.
149
+ * @returns
150
+ * Promise that resolves to an {@linkcode ArcjetDecision} indicating
151
+ * Arcjet’s decision about the request.
56
152
  */
57
- protect(request: ArcjetNodeRequest, ...props: Props extends WithoutCustomProps ? [] : [Props]): Promise<ArcjetDecision>;
153
+ protect(request: ArcjetNodeRequest, ...props: MaybeProperties<Props>): Promise<ArcjetDecision>;
58
154
  /**
59
- * Augments the client with another rule. Useful for varying rules based on
60
- * criteria in your handler—e.g. different rate limit for logged in users.
155
+ * Augment the client with another rule.
156
+ *
157
+ * Useful for varying rules based on criteria in your handler such as
158
+ * different rate limit for logged in users.
61
159
  *
62
- * @param rule The rule to add to this execution.
63
- * @returns An augmented {@link ArcjetNode} client.
160
+ * @template Rule
161
+ * Type of rule.
162
+ * @param rule
163
+ * Rule to add to Arcjet.
164
+ * @returns
165
+ * Arcjet instance augmented with the given rule.
64
166
  */
65
- withRule<Rule extends Primitive | Product>(rule: Rule): ArcjetNode<Simplify<Props & ExtraProps<Rule>>>;
167
+ withRule<ChildProperties extends PlainObject>(rule: Primitive<ChildProperties> | Product<ChildProperties>): ArcjetNode<Props & ChildProperties>;
66
168
  }
67
169
  /**
68
- * Create a new {@link ArcjetNode} client. Always build your initial client
69
- * outside of a request handler so it persists across requests. If you need to
70
- * augment a client inside a handler, call the `withRule()` function on the base
71
- * client.
170
+ * Create a new Node.js integration of Arcjet.
171
+ *
172
+ * > 👉 **Tip**:
173
+ * > build your initial base client with as many rules as possible outside of a
174
+ * > request handler;
175
+ * > if you need more rules inside handlers later then you can call `withRule()`
176
+ * > on that base client.
72
177
  *
73
- * @param options - Arcjet configuration options to apply to all requests.
178
+ * @template Rules
179
+ * List of rules.
180
+ * @template Characteristics
181
+ * Characteristics to track a user by.
182
+ * @param options
183
+ * Configuration.
184
+ * @returns
185
+ * Node.js integration of Arcjet.
74
186
  */
75
- export default function arcjet<const Rules extends (Primitive | Product)[], const Characteristics extends readonly string[]>(options: ArcjetOptions<Rules, Characteristics>): ArcjetNode<Simplify<ExtraProps<Rules> & CharacteristicProps<Characteristics>>>;
187
+ export default function arcjet<const Rules extends (Primitive | Product)[], const Characteristics extends readonly string[]>(options: ArcjetOptions<Rules, Characteristics>): ArcjetNode<ExtraProps<Rules> & CharacteristicProps<Characteristics>>;
package/index.js CHANGED
@@ -1,7 +1,8 @@
1
+ import * as core from 'arcjet';
1
2
  import core__default from 'arcjet';
2
3
  export * from 'arcjet';
3
- import findIP, { parseProxy } from '@arcjet/ip';
4
- import ArcjetHeaders from '@arcjet/headers';
4
+ import findIp, { parseProxy } from '@arcjet/ip';
5
+ import { ArcjetHeaders } from '@arcjet/headers';
5
6
  import { logLevel, isDevelopment, baseUrl, platform } from '@arcjet/env';
6
7
  import { Logger } from '@arcjet/logger';
7
8
  import { createClient } from '@arcjet/protocol/client.js';
@@ -27,9 +28,6 @@ const env = {
27
28
  get NODE_ENV() {
28
29
  return process.env.NODE_ENV;
29
30
  },
30
- get ARCJET_KEY() {
31
- return process.env.ARCJET_KEY;
32
- },
33
31
  get ARCJET_ENV() {
34
32
  return process.env.ARCJET_ENV;
35
33
  },
@@ -39,32 +37,26 @@ const env = {
39
37
  get ARCJET_BASE_URL() {
40
38
  return process.env.ARCJET_BASE_URL;
41
39
  },
40
+ get FIREBASE_CONFIG() {
41
+ return process.env.FIREBASE_CONFIG;
42
+ },
42
43
  };
43
- // TODO: Deduplicate with other packages
44
- function errorMessage(err) {
45
- if (err) {
46
- if (typeof err === "string") {
47
- return err;
48
- }
49
- if (typeof err === "object" &&
50
- "message" in err &&
51
- typeof err.message === "string") {
52
- return err.message;
53
- }
54
- }
55
- return "Unknown problem";
56
- }
44
+ let warnedForAutomaticBodyRead = false;
45
+ /**
46
+ * Create a remote client.
47
+ *
48
+ * @param options
49
+ * Configuration (optional).
50
+ * @returns
51
+ * Client.
52
+ */
57
53
  function createRemoteClient(options) {
58
- // The base URL for the Arcjet API. Will default to the standard production
59
- // API unless environment variable `ARCJET_BASE_URL` is set.
60
54
  const url = options?.baseUrl ?? baseUrl(env);
61
- // The timeout for the Arcjet API in milliseconds. This is set to a low value
62
- // in production so calls fail open.
63
55
  const timeout = options?.timeout ?? (isDevelopment(env) ? 1000 : 500);
64
56
  // Transport is the HTTP client that the client uses to make requests.
65
57
  const transport = createTransport(url);
66
58
  const sdkStack = "NODEJS";
67
- const sdkVersion = "1.0.0-beta.9";
59
+ const sdkVersion = "1.0.0";
68
60
  return createClient({
69
61
  transport,
70
62
  baseUrl: url,
@@ -84,12 +76,22 @@ function cookiesToString(cookies) {
84
76
  return cookies;
85
77
  }
86
78
  /**
87
- * Create a new {@link ArcjetNode} client. Always build your initial client
88
- * outside of a request handler so it persists across requests. If you need to
89
- * augment a client inside a handler, call the `withRule()` function on the base
90
- * client.
79
+ * Create a new Node.js integration of Arcjet.
91
80
  *
92
- * @param options - Arcjet configuration options to apply to all requests.
81
+ * > 👉 **Tip**:
82
+ * > build your initial base client with as many rules as possible outside of a
83
+ * > request handler;
84
+ * > if you need more rules inside handlers later then you can call `withRule()`
85
+ * > on that base client.
86
+ *
87
+ * @template Rules
88
+ * List of rules.
89
+ * @template Characteristics
90
+ * Characteristics to track a user by.
91
+ * @param options
92
+ * Configuration.
93
+ * @returns
94
+ * Node.js integration of Arcjet.
93
95
  */
94
96
  function arcjet(options) {
95
97
  const client = options.client ?? createRemoteClient();
@@ -109,10 +111,14 @@ function arcjet(options) {
109
111
  const cookies = cookiesToString(request.headers?.cookie);
110
112
  // We construct an ArcjetHeaders to normalize over Headers
111
113
  const headers = new ArcjetHeaders(request.headers);
112
- let ip = findIP({
113
- socket: request.socket,
114
- headers,
115
- }, { platform: platform(env), proxies });
114
+ const xArcjetIp = isDevelopment(env)
115
+ ? headers.get("x-arcjet-ip")
116
+ : undefined;
117
+ let ip = xArcjetIp ||
118
+ findIp({
119
+ socket: request.socket,
120
+ headers,
121
+ }, { platform: platform(env), proxies });
116
122
  if (ip === "") {
117
123
  // If the `ip` is empty but we're in development mode, we default the IP
118
124
  // so the request doesn't fail.
@@ -167,62 +173,41 @@ function arcjet(options) {
167
173
  };
168
174
  }
169
175
  function withClient(aj) {
170
- return Object.freeze({
176
+ const client = {
171
177
  withRule(rule) {
172
178
  const client = aj.withRule(rule);
173
179
  return withClient(client);
174
180
  },
175
- async protect(request, ...[props]) {
176
- // TODO(#220): The generic manipulations get really mad here, so we cast
177
- // Further investigation makes it seem like it has something to do with
178
- // the definition of `props` in the signature but it's hard to track down
179
- const req = toArcjetRequest(request, props ?? {});
181
+ async protect(request, props) {
182
+ // Cast of `{}` because here we switch from `undefined` to `Properties`.
183
+ const req = toArcjetRequest(request, props || {});
180
184
  const getBody = async () => {
181
- try {
182
- // If request.body is present then the body was likely read by a package like express' `body-parser`.
183
- // If it's not present then we attempt to read the bytes from the IncomingMessage ourselves.
184
- if (typeof request.body === "string") {
185
- return request.body;
185
+ // Read the stream if the body is not present.
186
+ if (request.body === null || request.body === undefined) {
187
+ let expectedLength;
188
+ // TODO: This shouldn't need to build headers again but the type
189
+ // for `req` above is overly relaxed
190
+ const headers = new ArcjetHeaders(request.headers);
191
+ const expectedLengthStr = headers.get("content-length");
192
+ if (typeof expectedLengthStr === "string") {
193
+ expectedLength = parseInt(expectedLengthStr, 10);
186
194
  }
187
- else if (typeof request.body !== "undefined" &&
188
- // BigInt cannot be serialized with JSON.stringify
189
- typeof request.body !== "bigint") {
190
- return JSON.stringify(request.body);
195
+ if (!warnedForAutomaticBodyRead) {
196
+ warnedForAutomaticBodyRead = true;
197
+ log.warn("Automatically reading the request body is deprecated; please pass an explicit `sensitiveInfoValue` field. See <https://docs.arcjet.com/upgrading/sdk-migration>.");
191
198
  }
192
- if (typeof request.on === "function" &&
193
- typeof request.removeListener === "function") {
194
- let expectedLength;
195
- // TODO: This shouldn't need to build headers again but the type
196
- // for `req` above is overly relaxed
197
- const headers = new ArcjetHeaders(request.headers);
198
- const expectedLengthStr = headers.get("content-length");
199
- if (typeof expectedLengthStr === "string") {
200
- try {
201
- expectedLength = parseInt(expectedLengthStr, 10);
202
- }
203
- catch {
204
- // If the expected length couldn't be parsed we'll just not set one.
205
- }
206
- }
207
- // Awaited to throw if it rejects and we'll just return undefined
208
- const body = await readBody(request, {
209
- // We will process 1mb bodies
210
- limit: 1048576,
211
- expectedLength,
212
- });
213
- return body;
214
- }
215
- log.warn("no body available");
216
- return;
199
+ return readBody(request, { expectedLength });
217
200
  }
218
- catch (e) {
219
- log.error("failed to get request body: %s", errorMessage(e));
220
- return;
201
+ // A package like `body-parser` was used to read the stream.
202
+ if (typeof request.body === "string") {
203
+ return request.body;
221
204
  }
205
+ return JSON.stringify(request.body);
222
206
  };
223
207
  return aj.protect({ getBody }, req);
224
208
  },
225
- });
209
+ };
210
+ return Object.freeze(client);
226
211
  }
227
212
  const aj = core__default({ ...options, client, log });
228
213
  return withClient(aj);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcjet/node",
3
- "version": "1.0.0-beta.9",
3
+ "version": "1.0.0",
4
4
  "description": "Arcjet SDK for Node.js",
5
5
  "keywords": [
6
6
  "analyze",
@@ -31,7 +31,7 @@
31
31
  "url": "https://arcjet.com"
32
32
  },
33
33
  "engines": {
34
- "node": ">=18"
34
+ "node": ">=20"
35
35
  },
36
36
  "type": "module",
37
37
  "main": "./index.js",
@@ -44,26 +44,28 @@
44
44
  "build": "rollup --config rollup.config.js",
45
45
  "lint": "eslint .",
46
46
  "prepublishOnly": "npm run build",
47
- "test": "npm run build && npm run lint"
47
+ "test-api": "node --test -- test/*.test.js",
48
+ "test-coverage#": "TODO: after node 20, use: ` --test-coverage-branches=100 --test-coverage-exclude \"../{analyze-wasm,analyze,arcjet,cache,duration,env,headers,ip,logger,protocol,runtime,sprintf,stable-hash,transport}/**/*.{js,ts}\" --test-coverage-exclude \"test/**/*.{js,ts}\" --test-coverage-functions=100 --test-coverage-lines=100`",
49
+ "test-coverage": "node --experimental-test-coverage --test -- test/*.test.js",
50
+ "test": "npm run build && npm run lint && npm run test-coverage"
48
51
  },
49
52
  "dependencies": {
50
- "@arcjet/env": "1.0.0-beta.9",
51
- "@arcjet/headers": "1.0.0-beta.9",
52
- "@arcjet/ip": "1.0.0-beta.9",
53
- "@arcjet/logger": "1.0.0-beta.9",
54
- "@arcjet/protocol": "1.0.0-beta.9",
55
- "@arcjet/transport": "1.0.0-beta.9",
56
- "@arcjet/body": "1.0.0-beta.9",
57
- "arcjet": "1.0.0-beta.9"
53
+ "@arcjet/env": "1.0.0",
54
+ "@arcjet/headers": "1.0.0",
55
+ "@arcjet/ip": "1.0.0",
56
+ "@arcjet/logger": "1.0.0",
57
+ "@arcjet/protocol": "1.0.0",
58
+ "@arcjet/transport": "1.0.0",
59
+ "@arcjet/body": "1.0.0",
60
+ "arcjet": "1.0.0"
58
61
  },
59
62
  "devDependencies": {
60
- "@arcjet/eslint-config": "1.0.0-beta.9",
61
- "@arcjet/rollup-config": "1.0.0-beta.9",
62
- "@arcjet/tsconfig": "1.0.0-beta.9",
63
- "@types/node": "18.18.0",
64
- "@rollup/wasm-node": "4.44.2",
65
- "eslint": "9.30.1",
66
- "typescript": "5.8.3"
63
+ "@arcjet/eslint-config": "1.0.0",
64
+ "@arcjet/rollup-config": "1.0.0",
65
+ "@types/node": "25.0.8",
66
+ "@rollup/wasm-node": "4.55.1",
67
+ "eslint": "9.39.2",
68
+ "typescript": "5.9.3"
67
69
  },
68
70
  "publishConfig": {
69
71
  "access": "public",