@sapporta/rest-core 3.52.1 → 3.52.2

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.
Files changed (60) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +75 -10
  3. package/index.cjs.d.ts +1 -0
  4. package/index.cjs.default.js +1 -0
  5. package/index.cjs.js +807 -0
  6. package/index.cjs.mjs +2 -0
  7. package/index.esm.js +762 -0
  8. package/package.json +13 -3
  9. package/src/lib/client.d.ts +107 -0
  10. package/src/lib/dsl.d.ts +222 -0
  11. package/src/lib/infer-types.d.ts +78 -0
  12. package/src/lib/paths.d.ts +30 -0
  13. package/src/lib/query.d.ts +17 -0
  14. package/src/lib/response-error.d.ts +20 -0
  15. package/src/lib/response-validation-error.d.ts +14 -0
  16. package/src/lib/server.d.ts +18 -0
  17. package/src/lib/standard-schema-utils.d.ts +68 -0
  18. package/src/lib/standard-schema.d.ts +55 -0
  19. package/src/lib/status-codes.d.ts +6 -0
  20. package/src/lib/{test-helpers.ts → test-helpers.d.ts} +1 -6
  21. package/src/lib/type-guards.d.ts +12 -0
  22. package/src/lib/type-utils.d.ts +96 -0
  23. package/src/lib/unknown-status-error.d.ts +10 -0
  24. package/src/lib/validation-error.d.ts +11 -0
  25. package/.babelrc +0 -10
  26. package/.eslintrc.json +0 -21
  27. package/LICENCE +0 -21
  28. package/jest.config.ts +0 -16
  29. package/project.json +0 -51
  30. package/src/lib/client.spec.ts +0 -1330
  31. package/src/lib/client.ts +0 -481
  32. package/src/lib/dsl.spec.ts +0 -1308
  33. package/src/lib/dsl.ts +0 -472
  34. package/src/lib/fetch.spec.ts +0 -102
  35. package/src/lib/infer-types.spec.ts +0 -935
  36. package/src/lib/infer-types.ts +0 -282
  37. package/src/lib/paths.spec.ts +0 -138
  38. package/src/lib/paths.ts +0 -61
  39. package/src/lib/query.spec.ts +0 -329
  40. package/src/lib/query.ts +0 -114
  41. package/src/lib/response-error.spec.ts +0 -67
  42. package/src/lib/response-error.ts +0 -61
  43. package/src/lib/response-validation-error.ts +0 -24
  44. package/src/lib/server.spec.ts +0 -163
  45. package/src/lib/server.ts +0 -83
  46. package/src/lib/standard-schema-utils.spec.ts +0 -218
  47. package/src/lib/standard-schema-utils.ts +0 -280
  48. package/src/lib/standard-schema.ts +0 -71
  49. package/src/lib/status-codes.ts +0 -75
  50. package/src/lib/type-guards.spec.ts +0 -355
  51. package/src/lib/type-guards.ts +0 -99
  52. package/src/lib/type-utils.spec.ts +0 -59
  53. package/src/lib/type-utils.ts +0 -234
  54. package/src/lib/unknown-status-error.ts +0 -15
  55. package/src/lib/validation-error.ts +0 -36
  56. package/tsconfig.json +0 -22
  57. package/tsconfig.lib.json +0 -10
  58. package/tsconfig.spec.json +0 -9
  59. package/typedoc.json +0 -5
  60. /package/src/{index.ts → index.d.ts} +0 -0
package/src/lib/client.ts DELETED
@@ -1,481 +0,0 @@
1
- import {
2
- AppRoute,
3
- AppRouteMutation,
4
- AppRouter,
5
- ContractNoBody,
6
- isAppRoute,
7
- } from './dsl';
8
- import { insertParamsIntoPath } from './paths';
9
- import { convertQueryParamsToUrlString } from './query';
10
- import { AreAllPropertiesOptional, Prettify } from './type-utils';
11
- import { UnknownStatusError } from './unknown-status-error';
12
- import {
13
- ClientInferRequest,
14
- ClientInferResponses,
15
- PartialClientInferRequest,
16
- } from './infer-types';
17
- import { Equal, Expect } from './test-helpers';
18
- import {
19
- isStandardSchema,
20
- parseAsStandardSchema,
21
- validateAgainstStandardSchema,
22
- } from './standard-schema-utils';
23
-
24
- type RecursiveProxyObj<T extends AppRouter, TClientArgs extends ClientArgs> = {
25
- [TKey in keyof T]: T[TKey] extends AppRoute
26
- ? AppRouteFunction<T[TKey], TClientArgs>
27
- : T[TKey] extends AppRouter
28
- ? RecursiveProxyObj<T[TKey], TClientArgs>
29
- : never;
30
- };
31
-
32
- /**
33
- * @deprecated Only safe to use on the client-side. Use `ServerInferResponses`/`ClientInferResponses` instead.
34
- */
35
- export type ApiResponseForRoute<T extends AppRoute> = ClientInferResponses<T>;
36
-
37
- /**
38
- * @deprecated Only safe to use on the client-side. Use `ServerInferResponses`/`ClientInferResponses` instead.
39
- */
40
- export function getRouteResponses<T extends AppRouter>(router: T) {
41
- return {} as ClientInferResponses<T>;
42
- }
43
-
44
- /**
45
- * Returned from a mutation or query call
46
- */
47
- export type AppRouteFunction<
48
- TRoute extends AppRoute,
49
- TClientArgs extends ClientArgs,
50
- TArgs = PartialClientInferRequest<TRoute, TClientArgs>,
51
- > = AreAllPropertiesOptional<TArgs> extends true
52
- ? (args?: Prettify<TArgs>) => Promise<Prettify<ClientInferResponses<TRoute>>>
53
- : (args: Prettify<TArgs>) => Promise<Prettify<ClientInferResponses<TRoute>>>;
54
-
55
- export type FetchOptions = typeof globalThis extends {
56
- Request: infer T extends typeof Request;
57
- }
58
- ? Omit<
59
- NonNullable<ConstructorParameters<T>[1]>,
60
- 'method' | 'headers' | 'body'
61
- >
62
- : never;
63
-
64
- export interface OverridableClientArgs {
65
- baseUrl: string;
66
- credentials?: FetchOptions['credentials'];
67
- jsonQuery?: boolean;
68
- validateResponse?: boolean;
69
- }
70
-
71
- export interface ClientArgs extends OverridableClientArgs {
72
- baseHeaders?: Record<string, string | ((options: FetchApiOptions) => string)>;
73
- api?: ApiFetcher;
74
- }
75
-
76
- export type ApiFetcherArgs<TFetchOptions extends FetchOptions = FetchOptions> =
77
- {
78
- route: AppRoute;
79
- path: string;
80
- method: string;
81
- headers: Record<string, string>;
82
- body: FormData | URLSearchParams | string | null | undefined;
83
- rawBody: unknown;
84
- rawQuery: unknown;
85
- contentType: AppRouteMutation['contentType'];
86
- fetchOptions?: FetchOptions;
87
- validateResponse?: boolean;
88
-
89
- /**
90
- * @deprecated Use `fetchOptions.credentials` instead
91
- */
92
- credentials?: TFetchOptions['credentials'];
93
- /**
94
- * @deprecated Use `fetchOptions.signal` instead
95
- */
96
- signal?: TFetchOptions['signal'];
97
- /**
98
- * @deprecated Use `fetchOptions.cache` instead
99
- */
100
- cache?: 'cache' extends keyof TFetchOptions
101
- ? TFetchOptions['cache']
102
- : never;
103
- /**
104
- * @deprecated Use `fetchOptions.next` instead
105
- */
106
- next?: 'next' extends keyof TFetchOptions ? TFetchOptions['next'] : never;
107
- };
108
-
109
- export type ApiFetcher = (args: ApiFetcherArgs) => Promise<{
110
- status: number;
111
- body: unknown;
112
- headers: Headers;
113
- }>;
114
-
115
- /**
116
- * Default fetch api implementation:
117
- *
118
- * Can be used as a reference for implementing your own fetcher,
119
- * or used in the "api" field of ClientArgs to allow you to hook
120
- * into the request to run custom logic
121
- */
122
- export const tsRestFetchApi: ApiFetcher = async ({
123
- route,
124
- path,
125
- method,
126
- headers,
127
- body,
128
- validateResponse,
129
- fetchOptions,
130
- }) => {
131
- const result = await fetch(path, {
132
- ...fetchOptions,
133
- method,
134
- headers,
135
- body,
136
- });
137
-
138
- const contentType = result.headers.get('content-type');
139
-
140
- if (contentType?.includes('application/') && contentType?.includes('json')) {
141
- const responseSchema = route.responses[result.status];
142
-
143
- const response = {
144
- status: result.status,
145
- body: responseSchema === ContractNoBody ? undefined : await result.json(),
146
- headers: result.headers,
147
- };
148
-
149
- const responseSchemaStandard = parseAsStandardSchema(responseSchema);
150
-
151
- if (
152
- responseSchemaStandard &&
153
- (validateResponse ?? route.validateResponseOnClient)
154
- ) {
155
- const result = validateAgainstStandardSchema(
156
- response.body,
157
- responseSchemaStandard,
158
- );
159
-
160
- if (result.error) {
161
- throw result.error;
162
- }
163
-
164
- return {
165
- ...response,
166
- body: result.value,
167
- };
168
- }
169
-
170
- return response;
171
- }
172
-
173
- if (contentType?.includes('text/')) {
174
- return {
175
- status: result.status,
176
- body: await result.text(),
177
- headers: result.headers,
178
- };
179
- }
180
-
181
- return {
182
- status: result.status,
183
- body: await result.blob(),
184
- headers: result.headers,
185
- };
186
- };
187
-
188
- const createFormData = (body: unknown) => {
189
- const formData = new FormData();
190
-
191
- const appendToFormData = (key: string, value: unknown) => {
192
- if (value instanceof File) {
193
- formData.append(key, value);
194
- } else {
195
- formData.append(key, JSON.stringify(value));
196
- }
197
- };
198
-
199
- Object.entries(body as Record<string, unknown>).forEach(([key, value]) => {
200
- if (Array.isArray(value)) {
201
- for (const item of value) {
202
- appendToFormData(key, item);
203
- }
204
- } else {
205
- appendToFormData(key, value);
206
- }
207
- });
208
-
209
- return formData;
210
- };
211
-
212
- const normalizeHeaders = (headers: Record<string, string | undefined>) => {
213
- return Object.fromEntries(
214
- Object.entries(headers).map(([k, v]) => [k.toLowerCase(), v]),
215
- );
216
- };
217
-
218
- export type FetchApiOptions = {
219
- path: string;
220
- clientArgs: ClientArgs;
221
- route: AppRoute;
222
- query: unknown;
223
- body: unknown;
224
- extraInputArgs: Record<string, unknown>;
225
- headers: Record<string, string | undefined>;
226
- fetchOptions?: FetchOptions;
227
- };
228
-
229
- export const fetchApi = (options: FetchApiOptions) => {
230
- const {
231
- path,
232
- clientArgs,
233
- route,
234
- body,
235
- query,
236
- extraInputArgs,
237
- headers,
238
- fetchOptions,
239
- } = options;
240
- const apiFetcher = clientArgs.api || tsRestFetchApi;
241
-
242
- const baseHeaders =
243
- clientArgs.baseHeaders &&
244
- Object.fromEntries(
245
- Object.entries(clientArgs.baseHeaders).map(([name, valueOrFunction]) => {
246
- if (typeof valueOrFunction === 'function') {
247
- return [name, valueOrFunction(options)];
248
- } else {
249
- return [name, valueOrFunction];
250
- }
251
- }),
252
- );
253
-
254
- const combinedHeaders = {
255
- ...(baseHeaders && normalizeHeaders(baseHeaders)),
256
- ...normalizeHeaders(headers),
257
- } as Record<string, string>;
258
-
259
- // Remove any headers that are set to undefined
260
- Object.keys(combinedHeaders).forEach((key) => {
261
- if (combinedHeaders[key] === undefined) {
262
- delete combinedHeaders[key];
263
- }
264
- });
265
-
266
- let fetcherArgs: ApiFetcherArgs = {
267
- route,
268
- path,
269
- method: route.method,
270
- headers: combinedHeaders,
271
- body: undefined,
272
- rawBody: body,
273
- rawQuery: query,
274
- contentType: undefined,
275
- validateResponse: clientArgs.validateResponse,
276
- fetchOptions: {
277
- ...(clientArgs.credentials && { credentials: clientArgs.credentials }),
278
- ...fetchOptions,
279
- },
280
- ...(fetchOptions?.signal && { signal: fetchOptions.signal }),
281
- ...(fetchOptions?.cache && { cache: fetchOptions.cache }),
282
- ...(fetchOptions &&
283
- 'next' in fetchOptions &&
284
- !!fetchOptions?.next && { next: fetchOptions.next as any }),
285
- };
286
-
287
- if (route.method !== 'GET') {
288
- if ('contentType' in route && route.contentType === 'multipart/form-data') {
289
- fetcherArgs = {
290
- ...fetcherArgs,
291
- contentType: 'multipart/form-data',
292
- body: body instanceof FormData ? body : createFormData(body),
293
- };
294
- } else if (
295
- 'contentType' in route &&
296
- route.contentType === 'application/x-www-form-urlencoded'
297
- ) {
298
- fetcherArgs = {
299
- ...fetcherArgs,
300
- contentType: 'application/x-www-form-urlencoded',
301
- headers: {
302
- 'content-type': 'application/x-www-form-urlencoded',
303
- ...fetcherArgs.headers,
304
- },
305
- body:
306
- typeof body === 'string'
307
- ? body
308
- : new URLSearchParams(
309
- body as Record<string, string> | URLSearchParams,
310
- ),
311
- };
312
- } else if (body !== null && body !== undefined) {
313
- fetcherArgs = {
314
- ...fetcherArgs,
315
- contentType: 'application/json',
316
- headers: {
317
- 'content-type': 'application/json',
318
- ...fetcherArgs.headers,
319
- },
320
- body: JSON.stringify(body),
321
- };
322
- }
323
- }
324
-
325
- return apiFetcher({
326
- ...fetcherArgs,
327
- ...extraInputArgs,
328
- });
329
- };
330
-
331
- export const evaluateFetchApiArgs = <TAppRoute extends AppRoute>(
332
- route: TAppRoute,
333
- clientArgs: InitClientArgs,
334
- inputArgs?: ClientInferRequest<AppRouteMutation, ClientArgs>,
335
- ) => {
336
- const {
337
- query,
338
- params,
339
- body,
340
- headers,
341
- extraHeaders,
342
- overrideClientOptions,
343
- fetchOptions,
344
-
345
- // TODO: remove in 4.0
346
- cache,
347
-
348
- // TODO: remove in 4.0
349
- next,
350
-
351
- // extra input args
352
- ...extraInputArgs
353
- } =
354
- (inputArgs as ClientInferRequest<AppRouteMutation, ClientArgs> & {
355
- next?: any;
356
- }) || {};
357
-
358
- // assert that we removed all non-extra args
359
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
360
- type AssertExtraInputArgsEmpty = Expect<Equal<typeof extraInputArgs, {}>>;
361
-
362
- const overriddenClientArgs = {
363
- ...clientArgs,
364
- ...overrideClientOptions,
365
- };
366
-
367
- /**
368
- * Coerce params to strings, allowing for numbers to be passed in (e.g. from using z.coerce.number())
369
- */
370
- const parsedParams =
371
- typeof params === 'object'
372
- ? Object.fromEntries(
373
- Object.entries(params).map(([key, value]) => [key, String(value)]),
374
- )
375
- : {};
376
-
377
- const completeUrl = getCompleteUrl(
378
- query,
379
- overriddenClientArgs.baseUrl,
380
- parsedParams,
381
- route,
382
- !!overriddenClientArgs.jsonQuery,
383
- );
384
-
385
- return {
386
- path: completeUrl,
387
- clientArgs: overriddenClientArgs,
388
- route,
389
- body,
390
- query,
391
- extraInputArgs,
392
- fetchOptions: {
393
- ...(cache && { cache }),
394
- ...(next && { next }),
395
- ...fetchOptions,
396
- },
397
- headers: {
398
- ...extraHeaders,
399
- ...headers,
400
- },
401
- } as Parameters<typeof fetchApi>[0];
402
- };
403
-
404
- /**
405
- * @hidden
406
- */
407
- export const getCompleteUrl = (
408
- query: unknown,
409
- baseUrl: string,
410
- params: Record<string, string>,
411
- route: AppRoute,
412
- jsonQuery: boolean,
413
- ) => {
414
- const path = insertParamsIntoPath({
415
- path: route.path,
416
- params: params,
417
- });
418
- const queryComponent = convertQueryParamsToUrlString(query, jsonQuery);
419
-
420
- if (baseUrl.endsWith('/') && path.startsWith('/')) {
421
- return `${baseUrl}${path.substring(1)}${queryComponent}`;
422
- }
423
-
424
- return `${baseUrl}${path}${queryComponent}`;
425
- };
426
-
427
- export const getRouteQuery = <TAppRoute extends AppRoute>(
428
- route: TAppRoute,
429
- clientArgs: InitClientArgs,
430
- ) => {
431
- const knownResponseStatuses = Object.keys(route.responses);
432
- return async (
433
- inputArgs?: ClientInferRequest<AppRouteMutation, ClientArgs>,
434
- ) => {
435
- const fetchApiArgs = evaluateFetchApiArgs(route, clientArgs, inputArgs);
436
- const response = await fetchApi(fetchApiArgs);
437
-
438
- // TODO: in next major version, throw by default if `strictStatusCode` is enabled
439
- if (!clientArgs.throwOnUnknownStatus) {
440
- return response;
441
- }
442
-
443
- if (knownResponseStatuses.includes(response.status.toString())) {
444
- return response;
445
- }
446
-
447
- throw new UnknownStatusError(response, knownResponseStatuses);
448
- };
449
- };
450
-
451
- export type InitClientReturn<
452
- T extends AppRouter,
453
- TClientArgs extends ClientArgs,
454
- > = RecursiveProxyObj<T, TClientArgs>;
455
-
456
- // TODO: in next major version, turn on by default if `strictStatusCode` is enabled and remove `throwOnUnknownStatus`
457
- export type InitClientArgs = ClientArgs & {
458
- /**
459
- * Ensures that the responses from the server match those defined in the
460
- * contract.
461
- */
462
- throwOnUnknownStatus?: boolean;
463
- };
464
-
465
- export const initClient = <
466
- T extends AppRouter,
467
- TClientArgs extends InitClientArgs,
468
- >(
469
- router: T,
470
- args: TClientArgs,
471
- ): InitClientReturn<T, TClientArgs> => {
472
- return Object.fromEntries(
473
- Object.entries(router).map(([key, subRouter]) => {
474
- if (isAppRoute(subRouter)) {
475
- return [key, getRouteQuery(subRouter, args)];
476
- } else {
477
- return [key, initClient(subRouter, args)];
478
- }
479
- }),
480
- );
481
- };