diff --git a/.github/workflows/size.yml b/.github/workflows/size.yml index cfbf25f..97c2ee8 100644 --- a/.github/workflows/size.yml +++ b/.github/workflows/size.yml @@ -9,6 +9,7 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: + node-version: 18 node-version-file: "package.json" cache: "npm" cache-dependency-path: "**/package-lock.json" diff --git a/package-lock.json b/package-lock.json index 80b5033..9aa16fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,13 +14,15 @@ "devDependencies": { "@babel/core": "^7.20.7", "@babel/preset-env": "^7.20.2", + "@babel/preset-typescript": "^7.18.6", "@types/jest": "^29.2.5", "@types/node": "^18.11.18", "cross-var": "^1.1.0", "eslint": "^8.30.0", "eslint-config-developit": "^1.2.0", "jest": "^29.3.1", - "microbundle": "^0.15.1" + "microbundle": "^0.15.1", + "typescript": "^4.9.4" } }, "node_modules/@ampproject/remapping": { @@ -1851,6 +1853,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz", + "integrity": "sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", @@ -2049,6 +2068,23 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/runtime": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", @@ -13921,6 +13957,17 @@ "@babel/helper-plugin-utils": "^7.18.9" } }, + "@babel/plugin-transform-typescript": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.20.7.tgz", + "integrity": "sha512-m3wVKEvf6SoszD8pu4NZz3PvfKRCMgk6D6d0Qi9hNnlM5M6CFS92EgF4EiHVLKbU0r/r7ty1hg7NPZwE7WRbYw==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.20.7", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + } + }, "@babel/plugin-transform-unicode-escapes": { "version": "7.18.10", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.18.10.tgz", @@ -14079,6 +14126,17 @@ "@babel/plugin-transform-react-pure-annotations": "^7.18.6" } }, + "@babel/preset-typescript": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.18.6.tgz", + "integrity": "sha512-s9ik86kXBAnD760aybBucdpnLsAt0jK1xqJn2juOn9lkOvSHV60os5hxoVJsPzMQxvnUJFAlkont2DvvaYEBtQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.18.6", + "@babel/helper-validator-option": "^7.18.6", + "@babel/plugin-transform-typescript": "^7.18.6" + } + }, "@babel/runtime": { "version": "7.20.7", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", diff --git a/package.json b/package.json index c64fd2f..7e412ad 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "jsnext:main": "./dist/unfetch.mjs", "umd:main": "./dist/unfetch.umd.js", "scripts": { - "test": "eslint && NODE_OPTIONS=--experimental-vm-modules jest", + "test": "eslint && tsc -p . --noEmit && NODE_OPTIONS=--experimental-vm-modules jest", "build": "microbundle src/index.mjs -f cjs,esm,umd && microbundle polyfill/polyfill.mjs -o polyfill/index.js -f cjs --no-sourcemap", "prepare": "npm run -s build", "release": "cross-var npm run build -s && cross-var git commit -am $npm_package_version && cross-var git tag $npm_package_version && git push && git push --tags && npm publish" @@ -53,24 +53,26 @@ "url": "http://localhost/" }, "testMatch": [ - "/test/**/*.test.?(m)js?(x)" + "/test/**/*.test.?(m)[jt]s?(x)" ], "setupFiles": [ "/test/_setup.js" ], "moduleFileExtensions": [ "mjs", - "js" + "js", + "ts" ], "transform": { - "^.+\\.m?jsx?$": "babel-jest" + "^.+\\.m?[jt]sx?$": "babel-jest" } }, "babel": { "env": { "test": { "presets": [ - "@babel/preset-env" + "@babel/preset-env", + "@babel/preset-typescript" ] } } @@ -78,12 +80,14 @@ "devDependencies": { "@babel/core": "^7.20.7", "@babel/preset-env": "^7.20.2", + "@babel/preset-typescript": "^7.18.6", "@types/jest": "^29.2.5", "@types/node": "^18.11.18", "cross-var": "^1.1.0", "eslint": "^8.30.0", "eslint-config-developit": "^1.2.0", "jest": "^29.3.1", - "microbundle": "^0.15.1" + "microbundle": "^0.15.1", + "typescript": "^4.9.4" } } diff --git a/src/index.d.ts b/src/index.d.ts index 7d582fa..b2350ce 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,45 +1,102 @@ import { - Body as NodeBody, - Headers as NodeHeaders, - Request as NodeRequest, - Response as NodeResponse, - RequestInit as NodeRequestInit + Body as NodeBody, + Headers as NodeHeaders, + Request as NodeRequest, + Response as NodeResponse, + RequestInit as NodeRequestInit, } from "node-fetch"; -declare namespace unfetch { - export type IsomorphicHeaders = Headers | NodeHeaders; - export type IsomorphicBody = Body | NodeBody; - export type IsomorphicResponse = Response | NodeResponse; - export type IsomorphicRequest = Request | NodeRequest; - export type IsomorphicRequestInit = RequestInit | NodeRequestInit; +/** @augments Headers */ +export interface UnfetchHeaders { + keys: () => string[]; + entries: () => [string, string][]; + get: (key: string) => string | null; + has: (key: string) => boolean; + + /** @deprecated not supported by unfetch */ + append: never; + /** @deprecated not supported by unfetch */ + delete: never; + /** @deprecated not supported by unfetch */ + forEach: never; + /** @deprecated not supported by unfetch */ + set: never; + /** @deprecated not supported by unfetch */ + values: never; + /** @deprecated not supported by unfetch */ + [Symbol.iterator]: never; } -type UnfetchResponse = { - ok: boolean, - statusText: string, - status: number, - url: string, - text: () => Promise, - json: () => Promise, - blob: () => Promise, - clone: () => UnfetchResponse, - headers: { - keys: () => string[], - entries: () => Array<[string, string]>, - get: (key: string) => string | undefined, - has: (key: string) => boolean, - } +/** @augments Response */ +export interface UnfetchResponse { + ok: boolean; + statusText: string; + status: number; + url: string; + text: () => Promise; + json: () => Promise; + blob: () => Promise; + clone: () => UnfetchResponse; + headers: UnfetchHeaders; + + /** @deprecated not supported by unfetch */ + arrayBuffer: never; + /** @deprecated not supported by unfetch */ + body: never; + /** @deprecated not supported by unfetch */ + bodyUsed: never; + /** @deprecated not supported by unfetch */ + formData: never; + /** @deprecated not supported by unfetch */ + redirected: never; + /** @deprecated not supported by unfetch */ + type: never; +} + +/** @augments RequestInit */ +export interface UnfetchRequestInit { + method?: string; + headers?: Record; + credentials?: "include" | "omit"; + body?: Parameters[0]; + + /** @deprecated not supported by unfetch */ + cache?: never; + /** @deprecated not supported by unfetch */ + integrity?: never; + /** @deprecated not supported by unfetch */ + keepalive?: never; + /** @deprecated not supported by unfetch */ + mode?: never; + /** @deprecated not supported by unfetch */ + redirect?: never; + /** @deprecated not supported by unfetch */ + referrer?: never; + /** @deprecated not supported by unfetch */ + referrerPolicy?: never; + /** @deprecated not supported by unfetch */ + signal?: never; + /** @deprecated not supported by unfetch */ + window?: never; } -type Unfetch = ( - url: string, - options?: { - method?: string, - headers?: Record, - credentials?: 'include' | 'omit', - body?: Parameters[0] - } -) => Promise +export namespace Unfetch { + export type IsomorphicHeaders = Headers | NodeHeaders; + export type IsomorphicBody = Body | NodeBody; + export type IsomorphicResponse = Response | NodeResponse; + export type IsomorphicRequest = Request | NodeRequest; + export type IsomorphicRequestInit = RequestInit | NodeRequestInit; + + export type Headers = UnfetchHeaders | globalThis.Headers; + export type Body = globalThis.Body; + export type Response = UnfetchResponse | globalThis.Response; + export type Request = UnfetchRequestInit | globalThis.Request; + export type RequestInit = UnfetchRequestInit | globalThis.RequestInit; +} + +export interface Unfetch { + (url: string | URL, options?: UnfetchRequestInit): Promise; +} declare const unfetch: Unfetch; diff --git a/test/typescript.test.ts b/test/typescript.test.ts new file mode 100644 index 0000000..0bd3946 --- /dev/null +++ b/test/typescript.test.ts @@ -0,0 +1,58 @@ +import unfetch, { Unfetch } from ".."; +import isomorphicUnfetch from "../packages/isomorphic-unfetch"; + +describe("TypeScript", () => { + describe("browser", () => { + beforeAll(() => { + function XMLHttpRequest() { + const res = { + setRequestHeader: jest.fn(), + getAllResponseHeaders: jest.fn().mockReturnValue(""), + getResponseHeader: jest.fn().mockReturnValue(""), + open: jest.fn((method, url) => { + res.responseURL = url; + }), + send: jest.fn(), + status: 200, + statusText: "OK", + get responseText() { + return this.responseURL.replace(/^data:\,/, ""); + }, + responseURL: null, + onload: () => {}, + }; + setTimeout(() => res.onload()); + return res; + } + + // @ts-ignore-next-line + global.XMLHttpRequest = jest.fn(XMLHttpRequest); + }); + + it("should have valid TypeScript types", async () => { + const res: Unfetch.Response = await unfetch("data:,test"); + const text = await res.text(); + expect(text).toBe("test"); + }); + + // This fails because we're abusing Arrays as iterables: + // it("should allow cast to Response", async () => { + // const res: Response = await unfetch("data:,test"); + // const r = res.headers.keys()[0] + // }); + }); + + describe("isomorphic-unfetch", () => { + it("should allow use of standard types like Response", async () => { + const res: Response = await isomorphicUnfetch(new URL("data:,test")); + const blob: Blob = await res.blob(); + }); + + it("should accept Headers", async () => { + isomorphicUnfetch("data:,test", { + headers: new Headers({ a: "b" }), + mode: "cors", + }); + }); + }); +}); diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..841e0b1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "noEmit": true, + "checkJs": true, + "target": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + }, + "include": [ + "./**/*.ts" + ] +}