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

Improve compatibility #68

Merged
merged 54 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
3a415c7
docs: Add Compatibility and Polyfills section
MattCCC Sep 22, 2024
ed67751
feat: Bring back widely used es2018
MattCCC Sep 22, 2024
95fc14c
feat: Add sideEffects false for more aggressive minification in bundlers
MattCCC Sep 22, 2024
f42e1e3
feat: Use default entry point on unpkg as module
MattCCC Sep 22, 2024
c2f8da3
feat: Bring back widely used es2018
MattCCC Sep 22, 2024
c8ed939
feat: Lock down build to minimum version of Node.js 18+ as per docs (…
MattCCC Sep 22, 2024
ec1214d
chore: Monitor size of Node.js build output when building
MattCCC Sep 22, 2024
502e87d
docs: Add section about Headers together with default headers being i…
MattCCC Sep 22, 2024
2ab3cb8
fix: Custom fetcher interceptors request methods typings should infer…
MattCCC Sep 22, 2024
ce9e1a4
fix: Polling error object typings - should infer Response
MattCCC Sep 22, 2024
ee3ee92
fix: Infer response data in request config when using createApiFetcher()
MattCCC Sep 22, 2024
383ddd4
fix: Infer response data in request config when using fetchf()
MattCCC Sep 22, 2024
70a3154
fix: Update typings:
MattCCC Sep 22, 2024
2e1d74c
fix: Update typings:
MattCCC Sep 22, 2024
d0189bd
docs: Add comments to Endpoint type
MattCCC Sep 22, 2024
e6a7110
docs: Add Request Chaining example
MattCCC Sep 22, 2024
1f54c05
fix: If `flattenResponse` is set to true, only the `data` is affected…
MattCCC Sep 22, 2024
3d8e6d4
fix: Set default response type to any instead of never
MattCCC Sep 22, 2024
58ab435
fix: Response typings when error is returned
MattCCC Sep 22, 2024
c060bb0
feat: Cancelled request can return ok: false
MattCCC Sep 22, 2024
30c2986
docs: Add section about Response Data Transformation
MattCCC Sep 22, 2024
b4ddd75
docs: Add section about Request Cancellation
MattCCC Sep 22, 2024
e5b4714
test: Add additional test with Request Cancellation using fetchf()
MattCCC Sep 22, 2024
ae5a1a0
docs: Add section about Request Cancellation
MattCCC Sep 22, 2024
3ef6417
docs: "Multiple API Specific Settings" info regarding general setting…
MattCCC Sep 23, 2024
dbbafde
docs: Revamp benefits
MattCCC Sep 23, 2024
0e98c7c
docs: Revamp benefits
MattCCC Sep 23, 2024
f14a0a2
docs: Revamp benefits
MattCCC Sep 23, 2024
0600954
docs: Revamp benefits
MattCCC Sep 23, 2024
abfcc6f
docs: Update JSDoc of API Handler interfaces
MattCCC Sep 23, 2024
1b2c86c
docs: Update generic Typings sections
MattCCC Sep 23, 2024
758082b
fix: Query Params, Url Path Params and Body Payload can infer whole g…
MattCCC Sep 23, 2024
7375ae0
docs: Fix import of typing in the example
MattCCC Sep 23, 2024
58c8a30
fix: Typings when Response Data is specified as generic
MattCCC Sep 24, 2024
8daa185
feat: Add possibility to overwrite Endpoint types with custom impleme…
MattCCC Sep 24, 2024
bcd7cd7
docs: Improve example of Automatic Request Cancellation + minor docs …
MattCCC Sep 24, 2024
d8550de
docs: Improve example of Automatic Request Cancellation + minor docs …
MattCCC Sep 24, 2024
3ecfcc6
docs: Prepare for v3 release
MattCCC Sep 24, 2024
0c704e2
chore: Upgrade dependencies
MattCCC Sep 27, 2024
f73b7c4
chore: Upgrade dependencies
MattCCC Sep 27, 2024
63cdf44
fix: Properly infer TS generics
MattCCC Sep 27, 2024
a922bc6
fix: Properly infer TS generics
MattCCC Sep 27, 2024
7a76c4e
fix: Properly type Request Body in configs
MattCCC Sep 29, 2024
42bb933
feat: Add Query Params, Path Params and Request Body generics to endp…
MattCCC Sep 30, 2024
9e9d80b
feat: Simplified endpoints API in createApiFetcher() - removed `query…
MattCCC Sep 30, 2024
f8ae36a
docs: Update docs and reflect settings changes
MattCCC Sep 30, 2024
d5d0192
docs: Add Plugin API Architecture
MattCCC Sep 30, 2024
d3da3d0
chore: Update dependencies
MattCCC Sep 30, 2024
808040d
fix: api.request() was not accepting custom URLs
MattCCC Sep 30, 2024
70aea50
perf: Don't remove withCredentials and data properties from final fet…
MattCCC Sep 30, 2024
22eee06
perf: Decrease size of final bundle
MattCCC Sep 30, 2024
f121610
test: Adjust build config test comparisons
MattCCC Sep 30, 2024
cf25450
test: Update examples to reflect new configuration
MattCCC Sep 30, 2024
7331eec
test: Update examples to reflect new configuration
MattCCC Sep 30, 2024
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
497 changes: 363 additions & 134 deletions README.md

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ currently being supported with security updates.

| Version | Supported |
| ------- | ------------------ |
| 2.5.x | :white_check_mark: |
| < 2.5 | :x: |
| 3.0.x | :white_check_mark: |
| < 3 | :x: |

## Reporting a Vulnerability

Expand Down
Binary file added docs/api-architecture.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
50 changes: 50 additions & 0 deletions docs/api-architecture.puml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
@startuml

!define RECTANGLE class

RECTANGLE GlobalConfig {
+baseURL: string
+fetcher: Fetcher
+endpoints: EndpointsSettings
...
[other RequestConfig settings]
}

note left of GlobalConfig
Configurations can be defined at three levels:
1. Global via createApiFetcher()
2. Per-endpoint in EndpointConfig
3. Per-request in fetchf() or api.yourEndpoint()
end note

RECTANGLE EndpointConfig {
+url: string
...
[other RequestConfig settings]
}

RECTANGLE RequestConfig {
+timeout: number
+params: object
+body: object
...
[all other RequestConfig settings]
}

RECTANGLE ApiFetcher {
+fetchf(endpointNameOrUrl: string, requestConfig: RequestConfig)
}

RECTANGLE ApiEndpoints {
+api.request(endpointNameOrUrl: string, requestConfig: RequestConfig)
+api.yourEndpoint(requestConfig: RequestConfig)
}

GlobalConfig --|> EndpointConfig : defines
EndpointConfig --|> RequestConfig : provides defaults
GlobalConfig ..> ApiEndpoints : applies globally
EndpointConfig ..> ApiEndpoints : applies globally
RequestConfig ..> ApiFetcher : per-request settings
RequestConfig ..> ApiEndpoints : per-request settings

@enduml
178 changes: 148 additions & 30 deletions docs/examples/examples.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/**
* This file contains various examples together with tests for typings declarations
*/
import { createApiFetcher, fetchf } from '../../src';
import type { Endpoint } from '../../src/types';

Expand Down Expand Up @@ -88,7 +91,9 @@ async function example3() {
fetchBooks: Endpoint<Books, BooksQueryParams>;
}

const api = createApiFetcher<Endpoints, typeof endpoints>({
type EndpointsConfiguration = typeof endpoints;

const api = createApiFetcher<Endpoints, EndpointsConfiguration>({
apiUrl: '',
endpoints,
});
Expand All @@ -100,45 +105,89 @@ async function example3() {
const { data } = await api.ping();

// Defined in EndpointsList with query param and url path param
const { data: book } = (await api.fetchBook(
{ newBook: true },
{ bookId: 1 },
)) satisfies Book;
const { data: book } = await api.fetchBook({
params: { newBook: true },
urlPathParams: { bookId: 1 },
});

// Defined in "endpoints" but not in EndpointsList. You don't need to add "fetchMovies: Endpoint;" explicitly.
// Defined in "endpoints" but not in EndpointsList so there is no need to add "fetchMovies: Endpoint;" explicitly.
const { data: movies1 } = await api.fetchMovies();

// With dynamically inferred type
const { data: movies } = await api.fetchMovies<Movies>();
const { data: movies3 }: { data: Movies } = await api.fetchMovies<Movies>();

// With custom params not defined in any interface
const { data: movies4 } = await api.fetchMovies({
params: {
all: true,
},
});

// @ts-expect-error This will result in an error as endpoint is not defined
const { data: movies2 } = await api.nonExistentEndpoint();

const { data: book1 } = (await api.fetchBook<Book>(
interface NewBook {
alternativeInterface: string;
}

interface NewBookQueryParams {
color: string;
}

// Overwrite response of existing endpoint
const { data: book1 } = await api.fetchBook<NewBook>(
{ newBook: true },
// @ts-expect-error should verify that bookId cannot be text
{ bookId: 'text' },
)) satisfies Book;
);

// Overwrite response and query params of existing endpoint
const { data: book11 } = await api.fetchBook<NewBook, NewBookQueryParams>({
params: {
// @ts-expect-error Should not allow old param
newBook: true,
color: 'green',
// TODO: @ts-expect-error Should not allow non-existent param
type: 'red',
},
});

// @ts-expect-error will result in an error since "someParams" is not defined
const { data: books } = (await api.fetchBooks({
someParams: 1,
})) satisfies Books;
// Standard fetch with predefined response and query params
const { data: books } = await api.fetchBooks({
// TODO: @ts-expect-error Non-existent setting
test: true,
params: {
// This param exists
all: true,
// @ts-expect-error Should not allow non-existent param
randomParam: 1,
},
});

const { data: book2 } = await api.fetchBook(
{ newBook: true },
// @ts-expect-error Error as bookId is not a number
{ bookId: 'text' },
);

const { data: book3 } = await api.fetchBook(
const { data: book3 } = await api.fetchBook({
// @ts-expect-error Error as newBook is not a boolean
{ newBook: 'true' },
{ bookId: 1 },
);
params: { newBook: 'true' },
urlPathParams: { bookId: 1 },
});

console.log('Example 3', data, apiConfig, endpointsList);
console.log('Example 3', movies, movies1, movies2, movies3);
console.log('Example 3', books, book, book1, book2, book3);
console.log('Example 3', movies, movies1, movies2, movies3, movies4);
console.log(
'Example 3',
books satisfies Books,
book satisfies Book,
book1 satisfies NewBook,
book11 satisfies NewBook,
book2 satisfies Book,
book3 satisfies Book,
);
}

// createApiFetcher() - direct API request() call to a custom endpoint with flattenResponse == true
Expand All @@ -147,35 +196,94 @@ async function example4() {
fetchBooks: Endpoint<Books, BooksQueryParams>;
}

const api = createApiFetcher<Endpoints, typeof endpoints>({
type EndpointsConfiguration = typeof endpoints;

const api = createApiFetcher<Endpoints, EndpointsConfiguration>({
apiUrl: '',
endpoints,
flattenResponse: true,
});

const books = await api.request<Books>('fetchBooks');
const data1 = await api.request('https://example.com/api/custom-endpoint');
// Existing endpoint generic
const { data: books } = await api.request<Books>('fetchBooks');

// Specify generic
const data2 = await api.request<{ myData: true }>(
// Custom URL
const { data: data1 } = await api.request(
'https://example.com/api/custom-endpoint',
);

const data3 = await fetchf<{ myData: true }>(
interface OtherEndpointData {
myData: true;
}

// Explicitly defined empty config
const { data: data4 } = await api.request('fetchBooks', {
params: {
anyParam: true,
},
});

// Dynamically added Response to a generic
const { data: data2 } = await api.request<OtherEndpointData>(
'https://example.com/api/custom-endpoint',
);

console.log('Example 4', books);
console.log('Example 4', data1, data2, data3);
// Dynamically added Response to a generic using fetchf()
const { data: data3 } = await fetchf<OtherEndpointData>(
'https://example.com/api/custom-endpoint',
);

// Existing endpoint with custom params
interface DynamicQueryParams {
param1: string;
}

interface DynamicUrlParams {
urlparam2: number;
}

const { data: books2 } = await api.request<
Books,
DynamicQueryParams,
DynamicUrlParams
>('fetchBooks', {
// Native fetch() setting
cache: 'no-store',
// Extended fetch setting
cacheTime: 86000,
// TODO: @ts-expect-error Non-existent setting
something: true,
urlPathParams: {
// @ts-expect-error Non-existent param
urlparam1: '1',
urlparam2: 1,
},
params: {
param1: '1',
// @ts-expect-error Non-existent param
param2: 1,
},
});

console.log('Example 4', books satisfies Books, books2 satisfies Books);
console.log(
'Example 4',
data1,
data2 satisfies OtherEndpointData,
data3 satisfies OtherEndpointData,
data4,
);
}

// createApiFetcher() - direct API request() call to a custom endpoint with flattenResponse == false
async function example5() {
interface Endpoints5 {
interface MyEndpoints {
fetchBooks: Endpoint<Books, BooksQueryParams>;
}

const api = createApiFetcher<Endpoints5, typeof endpoints>({
type EndpointsConfiguration = typeof endpoints;

const api = createApiFetcher<MyEndpoints, EndpointsConfiguration>({
apiUrl: '',
endpoints,
});
Expand All @@ -195,7 +303,7 @@ async function example5() {
console.log('Example 5', data1, data2);
}

// fetchf() - direct fetchf() request with flattenResponse == false
// fetchf() - direct fetchf() request
async function example6() {
const { data: books } = await fetchf<Books>('fetchBooks');
const { data: data1 } = await fetchf(
Expand All @@ -207,7 +315,17 @@ async function example6() {
'https://example.com/api/custom-endpoint',
);

console.log('Example 6', books);
// Fetch with custom settings
const { data: books2 } = await fetchf<Books>('fetchBooks', {
// Native fetch() setting
cache: 'no-store',
// Extended fetch setting
cacheTime: 86000,
// @ts-expect-error Non-existent setting
something: true,
});

console.log('Example 6', books satisfies Books, books2 satisfies Books);
console.log('Example 6', data1, data2);
}

Expand Down
Loading
Loading