@slango/ristretto 1.0.52 → 1.0.53

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.
@@ -1,180 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import {
4
- MiddlewareNext,
5
- PostResponseMiddlewareContext,
6
- PreRequestMiddlewareContext,
7
- usePostResponse,
8
- usePreRequest,
9
- } from '../middleware/index.js';
10
- import { HttpError } from './HttpError.js';
11
- import { HttpMethod } from './httpMethod.js';
12
- import { request, RequestOptions } from './request.js';
13
-
14
- describe('request', () => {
15
- const mockFetch = vi.fn();
16
- globalThis.fetch = mockFetch;
17
-
18
- afterEach(() => {
19
- mockFetch.mockClear();
20
- });
21
-
22
- const mockedResponseFactory = <T extends Record<string, unknown>>(data: T) => ({
23
- body: JSON.stringify(data),
24
- json: () => Promise.resolve(data),
25
- ok: true,
26
- });
27
-
28
- it('should make a successful GET request and return JSON response', async () => {
29
- const mockResponseData = { message: 'Success' };
30
- mockFetch.mockResolvedValueOnce(mockedResponseFactory(mockResponseData));
31
-
32
- const options: RequestOptions = {
33
- method: HttpMethod.GET,
34
- url: '/api/data',
35
- };
36
-
37
- const result = await request<typeof mockResponseData>(HttpMethod.GET, options);
38
-
39
- expect(mockFetch).toHaveBeenCalledOnce();
40
- expect(mockFetch).toHaveBeenCalledWith(`${window.location.origin}/api/data`, {
41
- headers: undefined,
42
- method: HttpMethod.GET,
43
- });
44
- expect(result).toEqual(mockResponseData);
45
- });
46
-
47
- it('should append query parameters to the URL', async () => {
48
- const mockResponseData = { message: 'Success with query params' };
49
- mockFetch.mockResolvedValueOnce(mockedResponseFactory(mockResponseData));
50
-
51
- const options: RequestOptions = {
52
- method: HttpMethod.GET,
53
- searchParams: new URLSearchParams({ page: '1', search: 'test' }),
54
- url: '/api/data',
55
- };
56
-
57
- const result = await request<typeof mockResponseData>(HttpMethod.GET, options);
58
-
59
- expect(mockFetch).toHaveBeenCalledOnce();
60
- expect(mockFetch).toHaveBeenCalledWith(
61
- `${window.location.origin}/api/data?page=1&search=test`,
62
- {
63
- headers: undefined,
64
- method: HttpMethod.GET,
65
- },
66
- );
67
- expect(result).toEqual(mockResponseData);
68
- });
69
-
70
- it('should throw an error if response is not ok', async () => {
71
- mockFetch.mockResolvedValueOnce({
72
- ok: false,
73
- status: 404,
74
- statusText: 'Not Found',
75
- });
76
-
77
- const options: RequestOptions = {
78
- method: HttpMethod.GET,
79
- url: '/api/notfound',
80
- };
81
-
82
- await expect(request(HttpMethod.GET, options)).rejects.toThrow(new HttpError(404, 'Not Found'));
83
- expect(mockFetch).toHaveBeenCalledOnce();
84
- expect(mockFetch).toHaveBeenCalledWith(`${window.location.origin}/api/notfound`, {
85
- headers: undefined,
86
- method: HttpMethod.GET,
87
- });
88
- });
89
-
90
- it('should include custom headers in the request', async () => {
91
- const mockResponseData = { message: 'Success with headers' };
92
- mockFetch.mockResolvedValueOnce(mockedResponseFactory(mockResponseData));
93
-
94
- const options: RequestOptions = {
95
- headers: {
96
- Authorization: 'Bearer test-token',
97
- 'Content-Type': 'application/json',
98
- },
99
- method: HttpMethod.GET,
100
- url: '/api/headers',
101
- };
102
-
103
- const result = await request<typeof mockResponseData>(HttpMethod.GET, options);
104
-
105
- expect(mockFetch).toHaveBeenCalledOnce();
106
- expect(mockFetch).toHaveBeenCalledWith(`${window.location.origin}/api/headers`, {
107
- headers: {
108
- Authorization: 'Bearer test-token',
109
- 'Content-Type': 'application/json',
110
- },
111
- method: HttpMethod.GET,
112
- });
113
- expect(result).toEqual(mockResponseData);
114
- });
115
-
116
- it('should send JSON body in POST request', async () => {
117
- const mockResponseData = { message: 'Success with JSON body' };
118
- mockFetch.mockResolvedValueOnce(mockedResponseFactory(mockResponseData));
119
-
120
- const options: RequestOptions = {
121
- body: JSON.stringify({ key: 'value' }),
122
- headers: {
123
- 'Content-Type': 'application/json',
124
- },
125
- method: HttpMethod.POST,
126
- url: '/api/post',
127
- };
128
-
129
- const result = await request<typeof mockResponseData>(HttpMethod.POST, options);
130
-
131
- expect(mockFetch).toHaveBeenCalledOnce();
132
- expect(mockFetch).toHaveBeenCalledWith(`${window.location.origin}/api/post`, {
133
- body: JSON.stringify({ key: 'value' }),
134
- headers: {
135
- 'Content-Type': 'application/json',
136
- },
137
- method: HttpMethod.POST,
138
- });
139
- expect(result).toEqual(mockResponseData);
140
- });
141
-
142
- it('should call middleware before and after the request', async () => {
143
- const mockResponseData = { message: 'Middleware test' };
144
- mockFetch.mockResolvedValueOnce({
145
- ...mockedResponseFactory({ ...mockResponseData }),
146
- status: 200,
147
- });
148
-
149
- const options: RequestOptions = {
150
- method: HttpMethod.GET,
151
- url: '/api/middleware',
152
- headers: {
153
- 'X-Test-Header': 'TestValue',
154
- },
155
- };
156
-
157
- const preRequestMiddleware = vi.fn(
158
- async (ctx: PreRequestMiddlewareContext, next: MiddlewareNext) => {
159
- expect(ctx.request.method).toBe(HttpMethod.GET);
160
- expect((ctx.request.headers! as Record<string, string>)['X-Test-Header']).toBe('TestValue');
161
- await next();
162
- },
163
- );
164
- const postResponseMiddleware = vi.fn(
165
- async (ctx: PostResponseMiddlewareContext, next: MiddlewareNext) => {
166
- expect(ctx.response.ok).toBe(true);
167
- expect(ctx.response.status).toBe(200);
168
- expect(ctx.response.body).toBe(JSON.stringify(mockResponseData));
169
- await next();
170
- },
171
- );
172
- usePreRequest(preRequestMiddleware);
173
- usePostResponse(postResponseMiddleware);
174
-
175
- await request<typeof mockResponseData>(HttpMethod.GET, options);
176
-
177
- expect(preRequestMiddleware).toHaveBeenCalledOnce();
178
- expect(postResponseMiddleware).toHaveBeenCalledOnce();
179
- });
180
- });
@@ -1,57 +0,0 @@
1
- import { runPostResponse, runPreRequest } from '../middleware/runner.js';
2
- import { AbortablePromise } from './abortableRequest.js';
3
- import { BaseURL, getDefaultBaseUrl } from './baseUrl.js';
4
- import { HttpError } from './HttpError.js';
5
- import { HttpMethod } from './httpMethod.js';
6
-
7
- type PathOnly = `../${string}` | `./${string}` | `/${string}`;
8
-
9
- export type RequestURL = BaseURL | PathOnly;
10
-
11
- export type Request<
12
- Response,
13
- PromiseType extends AbortablePromise<Response> | Promise<Response>,
14
- > = (options?: RequestOptionsWithoutUrl) => PromiseType;
15
-
16
- export type RequestOptions = RequestInit & {
17
- searchParams?: URLSearchParams;
18
- url: RequestURL;
19
- baseUrl?: BaseURL;
20
- };
21
-
22
- export type RequestOptionsWithoutUrl = Omit<RequestOptions, 'url'>;
23
-
24
- export const request = async <Response>(
25
- method: HttpMethod,
26
- options: RequestOptions,
27
- ): Promise<Response> => {
28
- const fetchOptions = {
29
- method,
30
- ...options,
31
- };
32
- const middlewareContext = { request: fetchOptions };
33
- await runPreRequest(middlewareContext);
34
-
35
- const { searchParams, url, baseUrl, ...requestOptions } = fetchOptions;
36
-
37
- const fullUrl = new URL(url, baseUrl || getDefaultBaseUrl());
38
- if (searchParams) {
39
- searchParams.forEach((value, key) => {
40
- fullUrl.searchParams.append(key, value);
41
- });
42
- }
43
-
44
- const response = await fetch(fullUrl.toString(), requestOptions);
45
-
46
- await runPostResponse({ ...middlewareContext, response });
47
-
48
- if (!response.ok) {
49
- throw new HttpError(response.status, response.statusText);
50
- }
51
-
52
- if (response.status === 204 || !response.body) {
53
- return undefined as Response;
54
- }
55
-
56
- return (await response.json()) as Response;
57
- };
@@ -1,66 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import { get } from '../index.js';
4
- import { HttpError } from './HttpError.js';
5
- import { HttpMethod } from './httpMethod.js';
6
- import { request, RequestOptionsWithoutUrl } from './request.js';
7
- import { withNotFoundAsNull } from './status.js';
8
-
9
- vi.mock('./request.js', () => ({
10
- request: vi.fn(),
11
- }));
12
-
13
- const requestMock = vi.mocked(request);
14
-
15
- const getRequest = get('/api/data/get');
16
-
17
- afterEach(() => {
18
- vi.clearAllMocks();
19
- });
20
-
21
- describe('withNotFoundAsNull', () => {
22
- it('should return the response when the request succeeds', async () => {
23
- const mockResponse = { data: 'valid response' };
24
- requestMock.mockResolvedValueOnce(mockResponse);
25
-
26
- const fetchData = withNotFoundAsNull(getRequest);
27
- const result = await fetchData();
28
-
29
- expect(result).toEqual(mockResponse);
30
- expect(requestMock).toHaveBeenCalledWith(HttpMethod.GET, { url: '/api/data/get' });
31
- });
32
-
33
- it('should return null when the request fails with 404', async () => {
34
- requestMock.mockRejectedValueOnce(new HttpError(404, 'Not Found'));
35
-
36
- const fetchData = withNotFoundAsNull(getRequest);
37
- const result = await fetchData();
38
-
39
- expect(result).toBeNull();
40
- expect(requestMock).toHaveBeenCalledWith(HttpMethod.GET, { url: '/api/data/get' });
41
- });
42
-
43
- it('should throw an error when the request fails with an error other than 404', async () => {
44
- requestMock.mockRejectedValueOnce(new HttpError(500, 'Internal Server Error'));
45
-
46
- const fetchData = withNotFoundAsNull(getRequest);
47
-
48
- await expect(fetchData()).rejects.toThrow(new HttpError(500, 'Internal Server Error'));
49
- expect(requestMock).toHaveBeenCalledWith(HttpMethod.GET, { url: '/api/data/get' });
50
- });
51
-
52
- it('should forward request options when calling the request function', async () => {
53
- const mockResponse = { data: 'valid response' };
54
- requestMock.mockResolvedValueOnce(mockResponse);
55
-
56
- const fetchData = withNotFoundAsNull(getRequest);
57
- const options: RequestOptionsWithoutUrl = { headers: { Authorization: 'Bearer token' } };
58
- const result = await fetchData(options);
59
-
60
- expect(result).toEqual(mockResponse);
61
- expect(requestMock).toHaveBeenCalledWith(HttpMethod.GET, {
62
- url: '/api/data/get',
63
- headers: { Authorization: 'Bearer token' },
64
- });
65
- });
66
- });
@@ -1,15 +0,0 @@
1
- import { HttpError } from './HttpError.js';
2
- import { RequestOptionsWithoutUrl } from './request.js';
3
-
4
- export const withNotFoundAsNull =
5
- <Response>(requestFn: (options?: RequestOptionsWithoutUrl) => Promise<Response>) =>
6
- async (options?: RequestOptionsWithoutUrl): Promise<null | Response> => {
7
- try {
8
- return await requestFn(options);
9
- } catch (error) {
10
- if (error instanceof HttpError && error.status == 404) {
11
- return null;
12
- }
13
- throw error;
14
- }
15
- };
@@ -1,53 +0,0 @@
1
- import { afterEach, describe, expect, it, vi } from 'vitest';
2
-
3
- import { HttpMethod } from './httpMethod.js';
4
- import { request, RequestOptionsWithoutUrl } from './request.js';
5
- import { withBaseUrl } from './withBaseUrl.js';
6
-
7
- describe('withBaseUrl', () => {
8
- const mockFetch = vi.fn();
9
- globalThis.fetch = mockFetch;
10
-
11
- afterEach(() => {
12
- mockFetch.mockClear();
13
- });
14
-
15
- const mockedResponseFactory = <T extends Record<string, unknown>>(data: T) => ({
16
- body: JSON.stringify(data),
17
- json: () => Promise.resolve(data),
18
- ok: true,
19
- });
20
-
21
- const baseRequest = (options?: RequestOptionsWithoutUrl) =>
22
- request<{ message: string }>(HttpMethod.GET, { ...options, url: '/data' });
23
-
24
- it('should apply default base URL when none provided in options', async () => {
25
- const req = withBaseUrl('https://api.example.com')(baseRequest);
26
- const mockResponseData = { message: 'Success' };
27
- mockFetch.mockResolvedValueOnce(mockedResponseFactory(mockResponseData));
28
-
29
- const result = await req();
30
-
31
- expect(mockFetch).toHaveBeenCalledOnce();
32
- expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/data', {
33
- headers: undefined,
34
- method: HttpMethod.GET,
35
- });
36
- expect(result).toEqual(mockResponseData);
37
- });
38
-
39
- it('should allow overriding base URL in options', async () => {
40
- const req = withBaseUrl('https://api.example.com')(baseRequest);
41
- const mockResponseData = { message: 'Override' };
42
- mockFetch.mockResolvedValueOnce(mockedResponseFactory(mockResponseData));
43
-
44
- const result = await req({ baseUrl: 'https://override.example.com' });
45
-
46
- expect(mockFetch).toHaveBeenCalledOnce();
47
- expect(mockFetch).toHaveBeenCalledWith('https://override.example.com/data', {
48
- headers: undefined,
49
- method: HttpMethod.GET,
50
- });
51
- expect(result).toEqual(mockResponseData);
52
- });
53
- });
@@ -1,12 +0,0 @@
1
- import type { BaseURL } from './baseUrl.js';
2
-
3
- import { AbortablePromise } from './abortableRequest.js';
4
- import { Request, RequestOptionsWithoutUrl } from './request.js';
5
-
6
- export const withBaseUrl =
7
- (baseUrl: BaseURL) =>
8
- <Response, PromiseType extends AbortablePromise<Response> | Promise<Response>>(
9
- req: Request<Response, PromiseType>,
10
- ): Request<Response, PromiseType> =>
11
- (options: RequestOptionsWithoutUrl = {}) =>
12
- req({ baseUrl, ...options });
@@ -1,10 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "extends": "./tsconfig.json",
4
- "compilerOptions": {
5
- "noEmit": false,
6
- "outDir": "./dist",
7
- "rootDir": "./src"
8
- },
9
- "exclude": ["node_modules", "dist", "src/**/*.spec.ts"]
10
- }
package/tsconfig.json DELETED
@@ -1,10 +0,0 @@
1
- {
2
- "$schema": "https://json.schemastore.org/tsconfig",
3
- "extends": "@slango.configs/typescript/default.json",
4
- "compilerOptions": {
5
- "noEmit": true,
6
- "types": ["node"]
7
- },
8
- "include": ["**/*.ts"],
9
- "exclude": ["node_modules", "dist"]
10
- }
package/vitest.config.js DELETED
@@ -1 +0,0 @@
1
- export { default } from '@slango.configs/vitest/browser';