@tpzdsp/next-toolkit 1.6.0 → 1.7.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.
- package/README.md +23 -46
- package/package.json +20 -3
- package/src/components/ErrorBoundary/ErrorFallback.stories.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorFallback.test.tsx +2 -2
- package/src/components/ErrorBoundary/ErrorFallback.tsx +21 -5
- package/src/components/Modal/Modal.tsx +2 -1
- package/src/components/skipLink/SkipLink.tsx +2 -1
- package/src/errors/ApiError.ts +97 -23
- package/src/http/constants.ts +111 -0
- package/src/http/fetch.ts +263 -0
- package/src/http/index.ts +6 -0
- package/src/http/logger.ts +163 -0
- package/src/http/proxy.ts +269 -0
- package/src/http/query.ts +287 -0
- package/src/http/stream.ts +77 -0
- package/src/types/api.ts +25 -0
- package/src/utils/http.ts +2 -30
- package/src/utils/schema.ts +30 -0
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BetterFetch,
|
|
3
|
+
type CreateFetchOption,
|
|
4
|
+
type FetchSchemaRoutes,
|
|
5
|
+
type FetchSchema,
|
|
6
|
+
type StandardSchemaV1,
|
|
7
|
+
type InferOptions,
|
|
8
|
+
type Schema,
|
|
9
|
+
type BetterFetchOption,
|
|
10
|
+
} from '@better-fetch/fetch';
|
|
11
|
+
import {
|
|
12
|
+
useQuery,
|
|
13
|
+
useSuspenseQuery,
|
|
14
|
+
type QueryKey,
|
|
15
|
+
type UseQueryOptions,
|
|
16
|
+
type UseQueryResult,
|
|
17
|
+
type UseSuspenseQueryOptions,
|
|
18
|
+
type UseSuspenseQueryResult,
|
|
19
|
+
} from '@tanstack/react-query';
|
|
20
|
+
|
|
21
|
+
import type { ApiError } from '../errors';
|
|
22
|
+
|
|
23
|
+
export type EmptyRoutePrefixes = '@get/' | '@post/' | '@put/' | '@patch/' | '@delete/';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* NOTE: In this file there are mentions of two kinds of schemas:
|
|
27
|
+
* - `validation schema`: A schema from a library like `zod`.
|
|
28
|
+
* - `fetch schema`: A schema from the `better-fetch` library that defines the endpoints of a specific API.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
// The library allows you to prefix routes with a special string to indicate the response type,
|
|
32
|
+
// but the implementation means they can show up on their own as valid routes, so we remove them to prevent that.
|
|
33
|
+
export type SchemaRoutes = Omit<FetchSchemaRoutes, EmptyRoutePrefixes>;
|
|
34
|
+
|
|
35
|
+
// Get any routes from a fetch schema that define a body as a validation schema.
|
|
36
|
+
type RoutesWithOutput<Routes> = {
|
|
37
|
+
[K in keyof Routes]: Routes[K] extends { output: unknown } ? K : never;
|
|
38
|
+
}[keyof Routes];
|
|
39
|
+
|
|
40
|
+
// Get any routes that DON'T define an output type as a validation schema by removing any that DO.
|
|
41
|
+
type RoutesWithoutOutput<Routes> = Exclude<keyof Routes, RoutesWithOutput<Routes>>;
|
|
42
|
+
|
|
43
|
+
// Get any routes from a fetch schema that define any inputs (body, query params, etc) as a validation schema.
|
|
44
|
+
type RoutesWithInput<Routes> = {
|
|
45
|
+
[K in keyof Routes]: Routes[K] extends
|
|
46
|
+
| { input?: unknown } // body
|
|
47
|
+
| { params?: unknown }
|
|
48
|
+
| { query?: unknown }
|
|
49
|
+
? K
|
|
50
|
+
: never;
|
|
51
|
+
}[keyof Routes];
|
|
52
|
+
|
|
53
|
+
// `better-fetch` options that prevent any body, query params, etc being passed in.
|
|
54
|
+
// This is `body` instead of `input` as it is overriding fields from the *options* NOT the schema.
|
|
55
|
+
type OptionsNoInputOutput = Omit<BetterFetchOption, 'query' | 'params' | 'body' | 'method'>;
|
|
56
|
+
|
|
57
|
+
// Get the options out of the schema, excluding the output type. This is basically the opposite of above, as it does
|
|
58
|
+
// include the inputs, like body, query params, etc.
|
|
59
|
+
export type OptionsWithInput<
|
|
60
|
+
Schema extends SchemaRoutes | undefined,
|
|
61
|
+
Route extends keyof Schema,
|
|
62
|
+
> = Schema extends SchemaRoutes
|
|
63
|
+
? Schema[Route] extends FetchSchema
|
|
64
|
+
? InferOptions<Schema[Route], Route>
|
|
65
|
+
: object
|
|
66
|
+
: object;
|
|
67
|
+
|
|
68
|
+
// Extracts the ouput of a validation schema given a list of routes from a fetch schema.
|
|
69
|
+
export type SchemaRouteOutput<
|
|
70
|
+
Schema extends SchemaRoutes | undefined,
|
|
71
|
+
Route extends keyof Schema,
|
|
72
|
+
> = Schema extends SchemaRoutes
|
|
73
|
+
? Schema[Route] extends FetchSchema
|
|
74
|
+
? Schema[Route]['output'] extends StandardSchemaV1
|
|
75
|
+
? StandardSchemaV1.InferOutput<Schema[Route]['output']>
|
|
76
|
+
: unknown
|
|
77
|
+
: unknown
|
|
78
|
+
: unknown;
|
|
79
|
+
|
|
80
|
+
// Extracts the routes of a fetch schema from a config object.
|
|
81
|
+
type SchemaRoutesOf<Option extends CreateFetchOption> = Option extends {
|
|
82
|
+
schema: { schema: infer Routes };
|
|
83
|
+
}
|
|
84
|
+
? Routes
|
|
85
|
+
: undefined;
|
|
86
|
+
|
|
87
|
+
// We provide the `queryFn` and `queryKeys` automatically, so no need to let the user provide them.
|
|
88
|
+
type OmitReactQueryKeys = 'queryKey' | 'queryFn';
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* A higher-order type that creates React Query hooks (`useBetterQuerySafe` and `useBetterSuspenseQuery`)
|
|
92
|
+
* from a given `BetterFetch` fetch instance.
|
|
93
|
+
*
|
|
94
|
+
* This type uses **function overloads** to support two different ways of determining the
|
|
95
|
+
* query’s output type:
|
|
96
|
+
*
|
|
97
|
+
* 1. **Schema-inferred output** — If the provided route in the fetch schema defines an `output`
|
|
98
|
+
* validation schema (e.g. a Zod schema), the hook automatically infers the result type from
|
|
99
|
+
* that schema. This applies to routes captured by `RoutesWithOutput`.
|
|
100
|
+
*
|
|
101
|
+
* ```ts
|
|
102
|
+
* const { useBetterQuerySafe } = reactQueryBetterFetch(api);
|
|
103
|
+
* const query = useBetterQuerySafe('/users'); // Output inferred from schema
|
|
104
|
+
* ```
|
|
105
|
+
*
|
|
106
|
+
* 2. **Manually specified output** — If the route does **not** define an `output` schema,
|
|
107
|
+
* the caller can manually specify the expected output type using the first generic parameter.
|
|
108
|
+
* This applies to routes captured by `RoutesWithoutOutput`.
|
|
109
|
+
*
|
|
110
|
+
* ```ts
|
|
111
|
+
* const query = useBetterQuerySafe<User[]>('/users'); // Output type provided manually
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* This dual overload approach provides flexibility — it enables strict type inference
|
|
115
|
+
* where schema information is available, while still allowing convenient explicit typing for routes
|
|
116
|
+
* that lack schema-based output definitions.
|
|
117
|
+
*/
|
|
118
|
+
type ReactQueryBetterFetch = <
|
|
119
|
+
Option extends CreateFetchOption & { schema: Schema },
|
|
120
|
+
Routes extends SchemaRoutes | undefined = SchemaRoutesOf<Option>,
|
|
121
|
+
>(
|
|
122
|
+
instance: BetterFetch<Option>,
|
|
123
|
+
) => {
|
|
124
|
+
// Safe query (error NOT thrown)
|
|
125
|
+
|
|
126
|
+
// Overload that uses schema output (`RoutesWithOutput`)
|
|
127
|
+
useBetterQuerySafe<
|
|
128
|
+
Error = ApiError,
|
|
129
|
+
Route extends RoutesWithOutput<Routes> = RoutesWithOutput<Routes>,
|
|
130
|
+
>(
|
|
131
|
+
route: Route,
|
|
132
|
+
// If the route requires an input, then expect options that provide that input, otherwise just expect options without inputs.
|
|
133
|
+
fetchOptions?: Route extends RoutesWithInput<Routes>
|
|
134
|
+
? OptionsWithInput<Routes, Route>
|
|
135
|
+
: OptionsNoInputOutput,
|
|
136
|
+
queryOptions?: Omit<
|
|
137
|
+
UseQueryOptions<unknown, Error, SchemaRouteOutput<Routes, Route>, QueryKey>,
|
|
138
|
+
OmitReactQueryKeys
|
|
139
|
+
>,
|
|
140
|
+
): UseQueryResult<SchemaRouteOutput<Routes, Route>, Error>;
|
|
141
|
+
|
|
142
|
+
// Overload that allows manual output type instead of schema (`RoutesWithoutOutput`)
|
|
143
|
+
useBetterQuerySafe<
|
|
144
|
+
Output,
|
|
145
|
+
Error = ApiError,
|
|
146
|
+
Route extends RoutesWithoutOutput<Routes> = RoutesWithoutOutput<Routes>,
|
|
147
|
+
>(
|
|
148
|
+
route: Route,
|
|
149
|
+
fetchOptions?: Route extends RoutesWithInput<Routes>
|
|
150
|
+
? OptionsWithInput<Routes, Route>
|
|
151
|
+
: OptionsNoInputOutput,
|
|
152
|
+
queryOptions?: Omit<UseQueryOptions<unknown, Error, Output, QueryKey>, OmitReactQueryKeys>,
|
|
153
|
+
): UseQueryResult<Output, Error>;
|
|
154
|
+
|
|
155
|
+
// Suspense query (error thrown)
|
|
156
|
+
|
|
157
|
+
// Overload that uses schema output (`RoutesWithOutput`)
|
|
158
|
+
useBetterSuspenseQuery<
|
|
159
|
+
Route extends RoutesWithOutput<Routes>,
|
|
160
|
+
Output = SchemaRouteOutput<Routes, Route>,
|
|
161
|
+
>(
|
|
162
|
+
route: Route,
|
|
163
|
+
fetchOptions?: Route extends RoutesWithInput<Routes>
|
|
164
|
+
? OptionsWithInput<Routes, Route>
|
|
165
|
+
: OptionsNoInputOutput,
|
|
166
|
+
queryOptions?: Omit<
|
|
167
|
+
UseSuspenseQueryOptions<unknown, never, Output, QueryKey>,
|
|
168
|
+
OmitReactQueryKeys
|
|
169
|
+
>,
|
|
170
|
+
): UseSuspenseQueryResult<Output>;
|
|
171
|
+
|
|
172
|
+
// Overload that allows manual output type instead of schema (`RoutesWithoutOutput`)
|
|
173
|
+
useBetterSuspenseQuery<
|
|
174
|
+
Output,
|
|
175
|
+
Route extends RoutesWithoutOutput<Routes> = RoutesWithoutOutput<Routes>,
|
|
176
|
+
>(
|
|
177
|
+
route: Route,
|
|
178
|
+
fetchOptions?: Route extends RoutesWithInput<Routes>
|
|
179
|
+
? OptionsWithInput<Routes, Route>
|
|
180
|
+
: OptionsNoInputOutput,
|
|
181
|
+
queryOptions?: Omit<
|
|
182
|
+
UseSuspenseQueryOptions<unknown, never, Output, QueryKey>,
|
|
183
|
+
OmitReactQueryKeys
|
|
184
|
+
>,
|
|
185
|
+
): UseSuspenseQueryResult<Output>;
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* A wrapper around React Query that integrates with `better-fetch` for fully typed,
|
|
190
|
+
* schema-validated data fetching.
|
|
191
|
+
*
|
|
192
|
+
* It provides two hooks:
|
|
193
|
+
* - `useBetterQuerySafe` – standard React Query hook (errors handled in result)
|
|
194
|
+
* - `useBetterSuspenseQuery` – suspense-based version (errors thrown)
|
|
195
|
+
*
|
|
196
|
+
* These hooks automatically infer input and output types from the provided `better-fetch`
|
|
197
|
+
* schema where possible. If a route does not define an output schema, you can manually
|
|
198
|
+
* specify the expected output type via the first generic parameter.
|
|
199
|
+
*
|
|
200
|
+
* ⚠️ **Type inference notes:**
|
|
201
|
+
* Because of the strict type relationships between schemas, routes, and query options,
|
|
202
|
+
* invalid parameter types or missing generic arguments can sometimes produce confusing
|
|
203
|
+
* TypeScript errors (e.g. mentioning `never` types).
|
|
204
|
+
* If you encounter this, double-check that:
|
|
205
|
+
* - The route key is valid for the given schema.
|
|
206
|
+
* - All fetch/query option types align with the route definition.
|
|
207
|
+
* - An explicit output type is provided when the schema does not define one.
|
|
208
|
+
*/
|
|
209
|
+
|
|
210
|
+
export const reactQueryBetterFetch: ReactQueryBetterFetch = <
|
|
211
|
+
Option extends CreateFetchOption,
|
|
212
|
+
Routes extends SchemaRoutes | undefined = SchemaRoutesOf<Option>,
|
|
213
|
+
>(
|
|
214
|
+
instance: BetterFetch<Option>,
|
|
215
|
+
) => {
|
|
216
|
+
// The types on `BetterFetch<Option>` are extremely strict, too strict to easily
|
|
217
|
+
// prevent errors if we just called `instance`, so cast it to `any` for now to
|
|
218
|
+
// prevent type errors at the call site.
|
|
219
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
220
|
+
const anyInstance = instance as any;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* React Query hook for performing a `better-fetch` request safely (errors are returned, not thrown).
|
|
224
|
+
*
|
|
225
|
+
* Automatically infers input and output types from the schema, or allows manual output typing.
|
|
226
|
+
* Wraps {@link https://tanstack.com/query/latest/docs/react/reference/useQuery useQuery} internally.
|
|
227
|
+
*
|
|
228
|
+
* @param route - The API route key defined in the `better-fetch` schema.
|
|
229
|
+
* @param fetchOptions - Optional request options (e.g. `body`, `params`, `query`) inferred from the schema.
|
|
230
|
+
* @param queryOptions - React Query options passed to `useQuery`, excluding `queryKey` and `queryFn`
|
|
231
|
+
* which are handled automatically.
|
|
232
|
+
*
|
|
233
|
+
* @returns A standard React Query result object (`UseQueryResult`), with strongly typed data and error.
|
|
234
|
+
*/
|
|
235
|
+
const useBetterQuerySafe = (
|
|
236
|
+
route: keyof Routes,
|
|
237
|
+
fetchOptions?: InferOptions<FetchSchema, ''>,
|
|
238
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
239
|
+
queryOptions?: Omit<UseQueryOptions<unknown, any, any, QueryKey>, OmitReactQueryKeys>,
|
|
240
|
+
) => {
|
|
241
|
+
return useQuery({
|
|
242
|
+
...queryOptions,
|
|
243
|
+
queryKey: [
|
|
244
|
+
route,
|
|
245
|
+
fetchOptions?.body,
|
|
246
|
+
fetchOptions?.params,
|
|
247
|
+
fetchOptions?.query,
|
|
248
|
+
fetchOptions?.headers,
|
|
249
|
+
],
|
|
250
|
+
queryFn: () => anyInstance(route, fetchOptions),
|
|
251
|
+
});
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* React Query hook for performing a `better-fetch` request using Suspense mode.
|
|
256
|
+
*
|
|
257
|
+
* Behaves like `useBetterQuerySafe` but integrates with React Suspense — errors are thrown instead of returned.
|
|
258
|
+
* Wraps {@link https://tanstack.com/query/latest/docs/react/reference/useSuspenseQuery useSuspenseQuery} internally.
|
|
259
|
+
*
|
|
260
|
+
* @param route - The API route key defined in the `better-fetch` schema.
|
|
261
|
+
* @param fetchOptions - Optional request options (e.g. `body`, `params`, `query`) inferred from the schema.
|
|
262
|
+
* @param queryOptions - React Query options passed to `useSuspenseQuery`, excluding `queryKey` and `queryFn`
|
|
263
|
+
* which are handled automatically.
|
|
264
|
+
*
|
|
265
|
+
* @returns A React Query suspense result (`UseSuspenseQueryResult`) with strongly typed data.
|
|
266
|
+
*/
|
|
267
|
+
const useBetterSuspenseQuery = <Route extends keyof Routes>(
|
|
268
|
+
route: Route,
|
|
269
|
+
fetchOptions?: InferOptions<FetchSchema, ''>,
|
|
270
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
271
|
+
queryOptions?: Omit<UseQueryOptions<unknown, never, any, QueryKey>, OmitReactQueryKeys>,
|
|
272
|
+
) => {
|
|
273
|
+
return useSuspenseQuery({
|
|
274
|
+
...queryOptions,
|
|
275
|
+
queryKey: [
|
|
276
|
+
route,
|
|
277
|
+
fetchOptions?.body,
|
|
278
|
+
fetchOptions?.params,
|
|
279
|
+
fetchOptions?.query,
|
|
280
|
+
fetchOptions?.headers,
|
|
281
|
+
],
|
|
282
|
+
queryFn: () => anyInstance(route, fetchOptions),
|
|
283
|
+
});
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
return { useBetterQuerySafe, useBetterSuspenseQuery };
|
|
287
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { HttpStatus } from '@tpzdsp/next-toolkit/http';
|
|
2
|
+
|
|
3
|
+
import { ApiError } from '../errors';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Reads streamed JSON X-Lines data from a response object, ignoring any invalid JSON lines.
|
|
7
|
+
*
|
|
8
|
+
* @param data Incoming response
|
|
9
|
+
* @returns Decoded JSON object.
|
|
10
|
+
*/
|
|
11
|
+
export const readJsonXLinesStream = async <T>(data: Response): Promise<T[]> => {
|
|
12
|
+
if (data.status === HttpStatus.NoContent) {
|
|
13
|
+
return [];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const reader = data.body?.getReader();
|
|
17
|
+
|
|
18
|
+
if (!reader) {
|
|
19
|
+
throw ApiError.badRequest('No readable stream available.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const decoder = new TextDecoder();
|
|
23
|
+
let buffer = '';
|
|
24
|
+
const parsedLines: T[] = [];
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
while (reader) {
|
|
28
|
+
const { value, done } = await reader.read();
|
|
29
|
+
|
|
30
|
+
if (done) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
buffer += decoder.decode(value, { stream: true });
|
|
35
|
+
buffer = processBufferLines<T>(buffer, parsedLines);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Handle any remaining buffered data
|
|
39
|
+
processFinalBuffer<T>(buffer, parsedLines);
|
|
40
|
+
} finally {
|
|
41
|
+
reader.releaseLock();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return parsedLines;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// Extract line processing logic to reduce complexity
|
|
48
|
+
const processBufferLines = <T>(buffer: string, parsedLines: T[]): string => {
|
|
49
|
+
const lines = buffer.split('\n');
|
|
50
|
+
const remainingBuffer = lines.pop() ?? ''; // leftover for next chunk
|
|
51
|
+
|
|
52
|
+
lines.forEach((line) => {
|
|
53
|
+
if (line.trim()) {
|
|
54
|
+
parseAndAddLine<T>(line, parsedLines);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return remainingBuffer;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
// Extract final buffer processing
|
|
62
|
+
const processFinalBuffer = <T>(buffer: string, parsedLines: T[]): void => {
|
|
63
|
+
if (buffer.trim()) {
|
|
64
|
+
parseAndAddLine<T>(buffer, parsedLines);
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// Extract JSON parsing logic
|
|
69
|
+
const parseAndAddLine = <T>(line: string, parsedLines: T[]): void => {
|
|
70
|
+
try {
|
|
71
|
+
const parsed = JSON.parse(line) as T;
|
|
72
|
+
|
|
73
|
+
parsedLines.push(parsed);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.warn('Invalid JSON line:', line, 'Error:', error);
|
|
76
|
+
}
|
|
77
|
+
};
|
package/src/types/api.ts
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
import z from 'zod/v4';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zod schema representing a standard API error response payload from
|
|
5
|
+
* external(non-proxy) APIs.
|
|
6
|
+
*
|
|
7
|
+
* - Validates that the API returns a `detail` field as a string.
|
|
8
|
+
* - Adds a default `message` for consistency when normalizing errors.
|
|
9
|
+
* - Used internally in `normalizeError` to convert external API errors
|
|
10
|
+
* into a consistent shape (`{ message, detail }`) that the application
|
|
11
|
+
* can safely work with.
|
|
12
|
+
*
|
|
13
|
+
* Note:
|
|
14
|
+
* - The schema does **not** include an HTTP status code; the status is
|
|
15
|
+
* determined separately based on the response or context.
|
|
16
|
+
*/
|
|
17
|
+
export const ErrorWithDetailSchema = z
|
|
18
|
+
.strictObject({
|
|
19
|
+
detail: z.string(),
|
|
20
|
+
})
|
|
21
|
+
.transform(({ detail }) => ({
|
|
22
|
+
details: detail,
|
|
23
|
+
message: 'An error occurred in the API.',
|
|
24
|
+
}));
|
|
25
|
+
|
|
1
26
|
// API related types
|
|
2
27
|
export type ApiResponse<T = unknown> = {
|
|
3
28
|
data: T;
|
package/src/utils/http.ts
CHANGED
|
@@ -1,34 +1,6 @@
|
|
|
1
|
+
import { HttpMethod, MimeType } from '../http/constants';
|
|
1
2
|
import type { Response, ApiFailure } from '../types/api';
|
|
2
3
|
|
|
3
|
-
export const MimeTypes = {
|
|
4
|
-
Json: 'application/json',
|
|
5
|
-
JsonLd: 'application/ld+json',
|
|
6
|
-
GeoJson: 'application/geo+json',
|
|
7
|
-
Png: 'image/png',
|
|
8
|
-
XJsonLines: 'application/x-jsonlines',
|
|
9
|
-
Csv: 'text/csv',
|
|
10
|
-
} as const;
|
|
11
|
-
|
|
12
|
-
export const Http = {
|
|
13
|
-
Ok: 200,
|
|
14
|
-
Created: 201,
|
|
15
|
-
NoContent: 204,
|
|
16
|
-
BadRequest: 400,
|
|
17
|
-
Unauthorized: 401,
|
|
18
|
-
Forbidden: 403,
|
|
19
|
-
NotFound: 404,
|
|
20
|
-
NotAllowed: 405,
|
|
21
|
-
InternalServerError: 500,
|
|
22
|
-
} as const;
|
|
23
|
-
|
|
24
|
-
export const HttpMethod = {
|
|
25
|
-
Get: 'GET',
|
|
26
|
-
Post: 'POST',
|
|
27
|
-
Put: 'PUT',
|
|
28
|
-
Patch: 'PATCH',
|
|
29
|
-
Delete: 'DELETE',
|
|
30
|
-
} as const;
|
|
31
|
-
|
|
32
4
|
type NodeGlobal = {
|
|
33
5
|
process?: {
|
|
34
6
|
env?: Record<string, string | undefined>;
|
|
@@ -133,7 +105,7 @@ const post = <Ok, Error = ApiFailure>(
|
|
|
133
105
|
method: HttpMethod.Post,
|
|
134
106
|
body: JSON.stringify(body),
|
|
135
107
|
headers: {
|
|
136
|
-
'Content-Type':
|
|
108
|
+
'Content-Type': MimeType.Json,
|
|
137
109
|
...options?.headers,
|
|
138
110
|
},
|
|
139
111
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { StandardSchemaV1 } from '@better-fetch/fetch';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a schema from a TypeScript type that does no validation on its input.
|
|
5
|
+
* This is useful if we want to define a schema for the sake of a type but don't actually want to run any validation on it.
|
|
6
|
+
*
|
|
7
|
+
* An example use is if we want to give query parameters types in a fetch schema, but don't need to validate them as the input
|
|
8
|
+
* values are coming from an input we control, so we know they are already valid. This would still allow the query params to have
|
|
9
|
+
* a typescript type but stops any validation for speed.
|
|
10
|
+
*
|
|
11
|
+
* @param _schema Input schema
|
|
12
|
+
* @returns The input schema but without validation
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export const typeOnlySchema = <Input = unknown, Output = Input>(): StandardSchemaV1<
|
|
16
|
+
Input,
|
|
17
|
+
Output
|
|
18
|
+
> => ({
|
|
19
|
+
'~standard': {
|
|
20
|
+
version: 1 as const,
|
|
21
|
+
vendor: 'no-validate',
|
|
22
|
+
validate: (value: unknown): StandardSchemaV1.Result<Output> => ({
|
|
23
|
+
value: value as Output,
|
|
24
|
+
}),
|
|
25
|
+
types: {
|
|
26
|
+
input: undefined as unknown as Input,
|
|
27
|
+
output: undefined as unknown as Output,
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
});
|