Skip to content

Commit fba8564

Browse files
fix(types): remove need for dom.iterable, dom, lib (#5047)
This commit changes our code to work with `@types/node` `18` through to the `24`, with and without `dom`, `dom.iterable`. Things *should* work in other runtimes, with the correct types for them, but that is difficult to verify as we use Node so much in our build to make sure things work. It’s been about 5 year of no movement on adding WebAssembly types to Node (<https://redirect.github.com/microsoft/TypeScript-DOM-lib-generator/issues/826>). We do not expose these types to users, so we can also inline the small API surface we need. The types of `Headers` are a bit different in undici (used in `@types/node`) and the DOM. I don’t know why. I refactored the code, to use properties instead of functions, and to call super methods through `Headers.prototype.*.call`. An esoteric difference. Someone subclassing and overwriting our types would have to do it similarly slightly differently too. But well, they should not use `ArcjetHeaders` if they are going to overwrite things, they could use `Headers` just as well. The changes *except* for `Headers` do not turn up in generated `.d.ts` files, so should not affect users. Importantly, there is a weird typescript thing (bug?) which will generate `import('undici-types).Headers` or `import('undici-types).Response` or so if return types are inferred. This was noticeable in `nosecone` and `nosecone-next`. That’s bad because we want *users* to choose where `Headers`, `Response`, etcetera are defined. The simple solution is to write return types. I do believe that *libraries* (not apps) should have explicit return types, as explicit contracts for what the API is, so I think that’s good anyway. Closes GH-16.
1 parent fdca6a9 commit fba8564

File tree

20 files changed

+120
-302
lines changed

20 files changed

+120
-302
lines changed

analyze-wasm/edge-light.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import componentCoreWasm from "./wasm/arcjet_analyze_js_req.component.core.wasm?
55
import componentCore2Wasm from "./wasm/arcjet_analyze_js_req.component.core2.wasm?module";
66
import componentCore3Wasm from "./wasm/arcjet_analyze_js_req.component.core3.wasm?module";
77

8-
async function moduleFromPath(path: string): Promise<WebAssembly.Module> {
8+
async function moduleFromPath(path: string): Promise<WebAssemblyLike.Module> {
99
if (path === "arcjet_analyze_js_req.component.core.wasm") {
1010
return componentCoreWasm;
1111
}

analyze-wasm/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ const componentCoreWasmPromise = componentCoreWasm();
99
const componentCore2WasmPromise = componentCore2Wasm();
1010
const componentCore3WasmPromise = componentCore3Wasm();
1111

12-
async function moduleFromPath(path: string): Promise<WebAssembly.Module> {
12+
async function moduleFromPath(path: string): Promise<WebAssemblyLike.Module> {
1313
if (path === "arcjet_analyze_js_req.component.core.wasm") {
1414
return componentCoreWasmPromise;
1515
}

analyze-wasm/wasm.d.ts

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,41 @@
1+
// Basic interface of WebAssembly that we use.
2+
// See:
3+
// <https://github.com/microsoft/TypeScript/blob/7956c0016/src/lib/dom.generated.d.ts#L37654>.
4+
// This only includes `Module` which we use here.
5+
// This can be removed when WebAssembly is in `@types/node` as we use that here.
6+
declare namespace WebAssemblyLike {
7+
type ImportExportKind = "function" | "global" | "memory" | "table";
8+
9+
interface ModuleExportDescriptor {
10+
kind: ImportExportKind;
11+
name: string;
12+
}
13+
14+
interface ModuleImportDescriptor {
15+
kind: ImportExportKind;
16+
module: string;
17+
name: string;
18+
}
19+
20+
interface Module {}
21+
22+
const Module: {
23+
prototype: Module;
24+
new (bytes: ArrayBufferView<ArrayBuffer> | ArrayBuffer): Module;
25+
customSections(moduleObject: Module, sectionName: string): ArrayBuffer[];
26+
exports(moduleObject: Module): ModuleExportDescriptor[];
27+
imports(moduleObject: Module): ModuleImportDescriptor[];
28+
};
29+
}
30+
131
/**
232
* Vercel uses the `.wasm?module` suffix to make WebAssembly available in their
333
* Vercel Functions product.
434
*
535
* https://vercel.com/docs/functions/wasm#using-a-webassembly-file
636
*/
737
declare module "*.wasm?module" {
8-
export default WebAssembly.Module;
38+
export default WebAssemblyLike.Module;
939
}
1040

1141
/**
@@ -16,13 +46,13 @@ declare module "*.wasm?module" {
1646
* https://developers.cloudflare.com/workers/runtime-apis/webassembly/javascript/#bundling
1747
*/
1848
declare module "*.wasm" {
19-
export default WebAssembly.Module;
49+
export default WebAssemblyLike.Module;
2050
}
2151

2252
/**
2353
* Our Rollup build turns `.wasm?js` imports into JS imports that provide the
2454
* `wasm()` function which decodes a base64 Data URL into a WebAssembly Module
2555
*/
2656
declare module "*.wasm?js" {
27-
export function wasm(): Promise<WebAssembly.Module>;
57+
export function wasm(): Promise<WebAssemblyLike.Module>;
2858
}

analyze-wasm/workerd.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import componentCoreWasm from "./wasm/arcjet_analyze_js_req.component.core.wasm"
55
import componentCore2Wasm from "./wasm/arcjet_analyze_js_req.component.core2.wasm";
66
import componentCore3Wasm from "./wasm/arcjet_analyze_js_req.component.core3.wasm";
77

8-
async function moduleFromPath(path: string): Promise<WebAssembly.Module> {
8+
async function moduleFromPath(path: string): Promise<WebAssemblyLike.Module> {
99
if (path === "arcjet_analyze_js_req.component.core.wasm") {
1010
return componentCoreWasm;
1111
}

arcjet-bun/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,11 @@
5656
"devDependencies": {
5757
"@arcjet/eslint-config": "1.0.0-beta.10",
5858
"@arcjet/rollup-config": "1.0.0-beta.10",
59-
"@types/node": "24.3.0",
6059
"@rollup/wasm-node": "4.50.0",
6160
"bun-types": "1.2.21",
6261
"eslint": "9.34.0",
63-
"typescript": "5.9.2"
62+
"typescript": "5.9.2",
63+
"undici-types": "7.15.0"
6464
},
6565
"publishConfig": {
6666
"access": "public",

examples/nodejs-rate-limit/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
{
22
"compilerOptions": {
33
"checkJs": true,
4-
"lib": ["dom.iterable", "dom", "es2022"],
4+
"lib": ["es2022"],
55
"module": "node16",
6+
"skipLibCheck": true,
67
"strict": true,
78
"target": "es2022"
89
},

headers/index.ts

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@ function isIterable(val: any): val is Iterable<any> {
22
return typeof val?.[Symbol.iterator] === "function";
33
}
44

5+
type HeadersInit =
6+
| Headers
7+
| Array<[string, string]>
8+
| Record<string, Array<string> | string | undefined>;
9+
510
/**
611
* Arcjet headers.
712
*
@@ -12,9 +17,7 @@ function isIterable(val: any): val is Iterable<any> {
1217
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Headers).
1318
*/
1419
export class ArcjetHeaders extends Headers {
15-
constructor(
16-
init?: HeadersInit | Record<string, string[] | string | undefined>,
17-
) {
20+
constructor(init?: HeadersInit | undefined) {
1821
super();
1922
if (
2023
typeof init !== "undefined" &&
@@ -26,9 +29,7 @@ export class ArcjetHeaders extends Headers {
2629
this.append(key, value);
2730
}
2831
} else {
29-
for (const [key, value] of Object.entries(
30-
init as Record<string, string[] | string | undefined>,
31-
)) {
32+
for (const [key, value] of Object.entries(init)) {
3233
if (typeof value === "undefined") {
3334
continue;
3435
}
@@ -58,15 +59,15 @@ export class ArcjetHeaders extends Headers {
5859
* @returns
5960
* Nothing.
6061
*/
61-
append(key: string, value: string): void {
62+
append: (key: string, value: string) => void = (key, value) => {
6263
if (typeof key !== "string" || typeof value !== "string") {
6364
return;
6465
}
6566

6667
if (key.toLowerCase() !== "cookie") {
67-
super.append(key, value);
68+
Headers.prototype.append.call(this, key, value);
6869
}
69-
}
70+
};
7071
/**
7172
* Set a header while ignoring `cookie`.
7273
*
@@ -80,15 +81,15 @@ export class ArcjetHeaders extends Headers {
8081
* @returns
8182
* Nothing.
8283
*/
83-
set(key: string, value: string): void {
84+
set: (key: string, value: string) => void = (key, value) => {
8485
if (typeof key !== "string" || typeof value !== "string") {
8586
return;
8687
}
8788

8889
if (key.toLowerCase() !== "cookie") {
89-
super.set(key, value);
90+
Headers.prototype.set.call(this, key, value);
9091
}
91-
}
92+
};
9293
}
9394

9495
/**

nosecone-next/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ function nextStyleSrc() {
6161
* Next.js middleware that sets secure headers.
6262
*/
6363
export function createMiddleware(options: Options = defaults) {
64-
return async () => {
64+
return async (): Promise<Response> => {
6565
const headers = nosecone(options);
6666
// Setting this specific header is the way that Next.js implements
6767
// middleware. See:

nosecone/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -943,7 +943,7 @@ export function createXssProtection() {
943943
* @returns
944944
* `Headers` with the configured security headers.
945945
*/
946-
export function nosecone(options?: Options | undefined) {
946+
export function nosecone(options?: Options | undefined): Headers {
947947
let contentSecurityPolicy =
948948
options?.contentSecurityPolicy ?? defaults.contentSecurityPolicy;
949949
let crossOriginEmbedderPolicy =

0 commit comments

Comments
 (0)