tempest-react-sdk 0.79.0 → 0.81.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 +35 -4
- package/bin/lib/component/generate.test.mjs +13 -1
- package/bin/lib/openapi/generate.hostile.test.mjs +58 -5
- package/bin/lib/openapi/generate.mjs +145 -34
- package/bin/lib/openapi/generate.test.mjs +216 -5
- package/bin/lib/openapi/schema-to-zod.mjs +4 -3
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +172 -103
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/br/boleto.cjs +1 -1
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js +2 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/br.d.ts +22 -3
- package/dist/components/AuthForm/AuthFieldControl.cjs +2 -0
- package/dist/components/AuthForm/AuthFieldControl.cjs.map +1 -0
- package/dist/components/AuthForm/AuthFieldControl.js +85 -0
- package/dist/components/AuthForm/AuthFieldControl.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.cjs +2 -0
- package/dist/components/AuthForm/AuthForm.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.js +83 -0
- package/dist/components/AuthForm/AuthForm.js.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.cjs +3 -0
- package/dist/components/AuthForm/AuthForm.module.cjs.map +1 -0
- package/dist/components/AuthForm/AuthForm.module.js +14 -0
- package/dist/components/AuthForm/AuthForm.module.js.map +1 -0
- package/dist/components/AuthForm/AuthLinks.cjs +2 -0
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -0
- package/dist/components/AuthForm/AuthLinks.js +29 -0
- package/dist/components/AuthForm/AuthLinks.js.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ForgotPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js +32 -0
- package/dist/components/AuthForm/ForgotPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/LoginForm.cjs +2 -0
- package/dist/components/AuthForm/LoginForm.cjs.map +1 -0
- package/dist/components/AuthForm/LoginForm.js +39 -0
- package/dist/components/AuthForm/LoginForm.js.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs +2 -0
- package/dist/components/AuthForm/ResetPasswordForm.cjs.map +1 -0
- package/dist/components/AuthForm/ResetPasswordForm.js +39 -0
- package/dist/components/AuthForm/ResetPasswordForm.js.map +1 -0
- package/dist/components/AuthForm/SignUpForm.cjs +2 -0
- package/dist/components/AuthForm/SignUpForm.cjs.map +1 -0
- package/dist/components/AuthForm/SignUpForm.js +32 -0
- package/dist/components/AuthForm/SignUpForm.js.map +1 -0
- package/dist/components/AuthForm/auth-defaults.cjs +2 -0
- package/dist/components/AuthForm/auth-defaults.cjs.map +1 -0
- package/dist/components/AuthForm/auth-defaults.js +54 -0
- package/dist/components/AuthForm/auth-defaults.js.map +1 -0
- package/dist/components/AuthForm/auth-schemas.cjs +2 -0
- package/dist/components/AuthForm/auth-schemas.cjs.map +1 -0
- package/dist/components/AuthForm/auth-schemas.js +26 -0
- package/dist/components/AuthForm/auth-schemas.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.cjs +2 -0
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.js +91 -0
- package/dist/components/AuthPage/AuthPage.js.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.cjs +3 -0
- package/dist/components/AuthPage/AuthPage.module.cjs.map +1 -0
- package/dist/components/AuthPage/AuthPage.module.js +24 -0
- package/dist/components/AuthPage/AuthPage.module.js.map +1 -0
- package/dist/components/FilterBar/filter-apply.cjs +1 -1
- package/dist/components/FilterBar/filter-apply.cjs.map +1 -1
- package/dist/components/FilterBar/filter-apply.js +7 -7
- package/dist/forms/index.js +7 -7
- package/dist/forms/masked-inputs.cjs +1 -1
- package/dist/forms/masked-inputs.cjs.map +1 -1
- package/dist/forms/masked-inputs.js +15 -15
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +104 -97
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/build-url.cjs +1 -1
- package/dist/http/build-url.cjs.map +1 -1
- package/dist/http/build-url.js +3 -1
- package/dist/http/build-url.js.map +1 -1
- package/dist/http/presigned-post.cjs +2 -0
- package/dist/http/presigned-post.cjs.map +1 -0
- package/dist/http/presigned-post.js +73 -0
- package/dist/http/presigned-post.js.map +1 -0
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +97 -97
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +12 -12
- package/dist/http/use-describe-api-error.cjs +1 -1
- package/dist/http/use-describe-api-error.cjs.map +1 -1
- package/dist/http/use-describe-api-error.js +9 -9
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +78 -56
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +44 -30
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +2 -0
- package/dist/sse/ticket-event-stream.cjs.map +1 -0
- package/dist/sse/ticket-event-stream.js +41 -0
- package/dist/sse/ticket-event-stream.js.map +1 -0
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +7 -4
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/styles/AuthForm.css +2 -0
- package/dist/styles/AuthPage.css +2 -0
- package/dist/styles/component/AuthForm.css +9 -0
- package/dist/styles/component/AuthPage.css +29 -0
- package/dist/styles/forms.css +36 -0
- package/dist/styles/manifest.json +63 -0
- package/dist/styles.css +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +124 -106
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw.d.ts +49 -15
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +809 -22
- package/dist/tempest-react-sdk.js +373 -363
- package/dist/utils/format.cjs +1 -1
- package/dist/utils/format.cjs.map +1 -1
- package/dist/utils/format.js +25 -25
- package/dist/utils/numbers.cjs +1 -1
- package/dist/utils/numbers.cjs.map +1 -1
- package/dist/utils/numbers.js +7 -7
- package/package.json +1 -1
- package/template-pwa/src/sw.ts +10 -6
package/README.md
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# tempest-react-sdk
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/tempest-react-sdk)
|
|
4
|
-
[](https://github.com/mauriciobenjamin700/tempest-react-sdk/actions/workflows/ci.yml)
|
|
5
4
|
[](./LICENSE)
|
|
6
5
|
[](https://react.dev)
|
|
7
6
|
[](https://www.typescriptlang.org/)
|
|
@@ -43,6 +42,7 @@ The goal is to start every new React frontend with the same opinionated foundati
|
|
|
43
42
|
- [Polling](#polling-recipe)
|
|
44
43
|
- [Retry & idempotency](#retry--idempotency-recipe)
|
|
45
44
|
- [Auth store (Zustand)](#auth-store-recipe)
|
|
45
|
+
- [Auth screens (login, sign-up, password reset)](#auth-screens-recipe)
|
|
46
46
|
- [Route guard](#route-guard-recipe)
|
|
47
47
|
- [JWT helpers & refresh queue](#jwt-helpers--refresh-queue-recipe)
|
|
48
48
|
- [Code-splitting with retry](#code-splitting-with-retry-recipe)
|
|
@@ -267,7 +267,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
267
267
|
|
|
268
268
|
| Module | Exports |
|
|
269
269
|
| ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
270
|
-
| `components` | `Avatar`, `Badge`, `Breadcrumbs`, `Button`, `Card`, `Checkbox`, `ChipInput`, `ConfirmDialog`, `Container`, `DatePicker`, `Drawer`, `EmptyState`, `ErrorState`, `FileUpload`, `Form` (`FormSection`, `FormRow`, `FormActions`), `Grid`, `Input`, `InstallBanner`, `InstallButton`, `Modal`, `Pagination`, `Progress`, `Radio`, `RadioGroup`, `SearchBar`, `Select`, `Skeleton`, `Spinner`, `Stack`, `Stepper`, `Switch`, `Table`, `Tabs`, `Textarea`, `Toast` (`ToastProvider`, `useToast`), `Tooltip`, `VirtualList`
|
|
270
|
+
| `components` | `AuthPage` + `AuthForm` with the presets `LoginForm`, `SignUpForm`, `ForgotPasswordForm`, `ResetPasswordForm` (auth screens from a list of fields), `Avatar`, `Badge`, `Breadcrumbs`, `Button`, `Card`, `Checkbox`, `ChipInput`, `ConfirmDialog`, `Container`, `DatePicker`, `Drawer`, `EmptyState`, `ErrorState`, `FileUpload`, `Form` (`FormSection`, `FormRow`, `FormActions`), `Grid`, `Input`, `InstallBanner`, `InstallButton`, `Modal`, `Pagination`, `Progress`, `Radio`, `RadioGroup`, `SearchBar`, `Select`, `Skeleton`, `Spinner`, `Stack`, `Stepper`, `Switch`, `Table`, `Tabs`, `Textarea`, `Toast` (`ToastProvider`, `useToast`), `Tooltip`, `VirtualList` |
|
|
271
271
|
| `hooks` | `useDebounce`, `usePagination`, `useClientFilter`, `useDraftFilters` (draft vs applied server-side filter), `useMediaQuery`, `useOnline`, `useDocumentVisibility`, `useIntersectionObserver`, `useResizeObserver`, `useClipboard`, `useKeyboardShortcut`, `useBeforeInstallPrompt`, `useIdle`, `useGeolocation`, `useScrollLock`, `useFocusTrap`, `useStableCallback`, `useAnnounce` (+ pure `announce`/`clearAnnouncer` — one shared live-region pair), `useDeepMemo`, `useObjectUrl`, `useLongPressHandlers` (spreadable long-press handlers + `wasLongPress()` guard), `useInstallPrompt` (install-method resolver with decline cooldown), `useServiceWorkerUpdate` (consent-based SW update flow), `useStorageEstimate` + `estimateStorage`/`requestPersistentStorage` (Storage API quota & persistence), PWA env helpers `isIOS`/`isAndroid`/`isAndroidWithoutPromptApi`/`isStandalone`/`buildOpenInChromeIntent`, type `BeforeInstallPromptEvent` |
|
|
272
272
|
| `http` | `createApiClient`, `buildApiUrl`, `csrfHeaders`, `parseResponse`, `uploadWithProgress`, `createResumableUpload` (tus 1.0.0: chunked + resumable, `pause`/`resume`/`abort`), `createLocalUploadStorage`, `uploadFingerprint`, `retry`, `generateIdempotencyKey`, `usePoll`, types: `ApiClient`, `ApiClientConfig`, `ApiError`, `BuildApiUrlOptions`, `CsrfOptions`, `RequestOptions`, `RetryOptions`, `UploadProgressEvent`, `UploadWithProgressOptions`, `UsePollOptions`, `UsePollResult` |
|
|
273
273
|
| `auth` _(peer: `zustand`)_ | `createAuthStore`, `AuthGuard`, `decodeJWT`, `isJWTExpired`, `lazyWithRetry`, `createRefreshQueue`, **passkeys/WebAuthn**: `createPasskeyClient`, `usePasskeyRegistration`, `usePasskeySignIn`, `usePasskeyCapabilities`, `isPasskeySupported`, `isPlatformAuthenticatorAvailable`, `isConditionalMediationAvailable`, `classifyPasskeyError`, `PasskeyError`, `base64UrlToBytes`, `bytesToBase64Url`, types: `AuthState`, `CreateAuthStoreOptions`, `AuthGuardProps`, `DecodedJWT`, `LazyWithRetryOptions` |
|
|
@@ -284,7 +284,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
284
284
|
| `geo` _(opt. peer `leaflet`)_ | `haversineKm`, `pathLengthKm`, `bearingDeg`, `estimateTravel`, `createOSRMBackend`, `boundingBox`, `projectMercator`, `fitProjection`, `createPositionTracker`, `usePositionTracker`, `TrajectoryMap` — tile-free SVG map + trajectory tracking (no external/paid API; Leaflet is an opt-in, lazy-loaded tile layer for self-hosted tiles), types: `Coordinate`, `TrackPoint`, `TravelEstimate`, `TravelMode`, `GeoBounds`, `RoutingBackend` |
|
|
285
285
|
| `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
|
|
286
286
|
| `push` | `WebPushClient`, `WebPushUnsupportedError`, `WebPushPermissionDeniedError`, `usePushSubscription`, `urlBase64ToUint8Array`, `isPushSupported` |
|
|
287
|
-
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`, credentials never stored), `provideBackgroundSyncCredentials`, `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability), `clearSessionData`/`clearBackgroundSyncQueue` (logout cleanup) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts`
|
|
287
|
+
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`, credentials never stored and re-applied only to the worker's origin or `trustedOrigins`), `provideBackgroundSyncCredentials`, `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability), `clearSessionData`/`clearBackgroundSyncQueue` (logout cleanup) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts` |
|
|
288
288
|
| `charts` _(subpath `tempest-react-sdk/charts`, opt. peer `recharts`)_ | `AreaChart`, `BarChart`, `LineChart`, `PieChart`, `RadarChart`, `DEFAULT_CHART_COLORS` — themed recharts wrappers (recharts externalized; install it only if you use charts) |
|
|
289
289
|
| `icons` _(subpath `tempest-react-sdk/icons`)_ | `Icon`, `IconProvider`, `createIconRegistry`, `useIcon`, `preloadIcons`, `iconStatus`, `peekIcon`, `loadIcon`, `resolveIconAlias`, `isIconName`, `iconNames`, `iconAliases`, type `IconName` — render any of lucide's **2065 icons by kebab-case slug**. A literal slug becomes a static import via the `tempestIcons()` Vite plugin (zero extra requests); a runtime slug loads **one of 46 balanced shards** (40 slugs each) instead of the ~2000 chunk boundaries `lucide-react`'s own `DynamicIcon` forces — sharding by range rather than by initial letter took the worst fetch from 19.10 KB to 4.78 KB brotli. Unknown slug renders `fallback` and never throws; lucide's 258 deprecated aliases keep resolving |
|
|
290
290
|
| `neural` _(subpath `tempest-react-sdk/neural`, opt. peer `@sapphi-red/web-noise-suppressor`)_ | `isNeuralSuppressionSupported`, `prepareNeuralSuppressor`, `createNeuralSuppressor`, types `NeuralSuppressorAssets`, `NeuralSuppressor` — RNNoise noise suppression: a recurrent model in an `AudioWorklet` separates speech from non-speech on a 48 kHz context and hands back a denoised `MediaStreamTrack` to publish in place of the capture (peer externalized; install it only if you use neural) |
|
|
@@ -321,7 +321,7 @@ Every module is re-exported from the package root — `import { Button, useDebou
|
|
|
321
321
|
| `br` _(subpath `tempest-react-sdk/br`)_ | `BrazilMap`, `BrazilStateMap`, `BrazilStateCitySelect`, `listStates`, `getState`, `citiesByUf`, `statesByRegion`, `ufChoices`, `cityChoices`, `isValidUf`, `normalizeUf`, `isValidCity`, `loadBrUfGeoJson`, `loadStateMunicipalities`, types `UF`, `BrRegion`, `BrazilState` — clickable 27-UF SVG map + per-state municipality submaps (bundled simplified IBGE GeoJSON, lazy per UF) + states/cities dataset, no external API; mirrors the FastAPI SDK `utils/locations` |
|
|
322
322
|
| `icons` _(subpath `tempest-react-sdk/icons`)_ | `Icon`, `IconProvider`, `createIconRegistry`, `useIcon`, `preloadIcons`, `iconStatus`, `peekIcon`, `loadIcon`, `resolveIconAlias`, `isIconName`, `iconNames`, `iconAliases`, type `IconName` — render any of lucide's **2065 icons by kebab-case slug**. A literal slug becomes a static import via the `tempestIcons()` Vite plugin (zero extra requests); a runtime slug loads **one of 46 balanced shards** (40 slugs each) instead of the ~2000 chunk boundaries `lucide-react`'s own `DynamicIcon` forces — sharding by range rather than by initial letter took the worst fetch from 19.10 KB to 4.78 KB brotli. Unknown slug renders `fallback` and never throws; lucide's 258 deprecated aliases keep resolving |
|
|
323
323
|
| `push` | `WebPushClient`, `WebPushUnsupportedError`, `WebPushPermissionDeniedError`, `usePushSubscription`, `urlBase64ToUint8Array`, `isPushSupported` |
|
|
324
|
-
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`, credentials never stored), `provideBackgroundSyncCredentials`, `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability), `clearSessionData`/`clearBackgroundSyncQueue` (logout cleanup) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts`
|
|
324
|
+
| `sw` _(also subpath `tempest-react-sdk/sw`)_ | `registerServiceWorker` (opt-in `autoUpdate` poll + reload, no `vite-plugin-pwa`), `skipWaiting`, `unregisterAllServiceWorkers`, `installPushHandler`, `installNotificationClickHandler`, `installSkipWaitingListener`, `installPrecache` (app-shell offline + Navigation Preload), `installRuntimeCache` (per-route caching, incl. `rangeRequests`), `createPartialResponse` (206 range slicing), `installBackgroundSync` (offline mutation queue + `periodicsync`, credentials never stored and re-applied only to the worker's origin or `trustedOrigins`), `provideBackgroundSyncCredentials`, `registerPeriodicSync` (main-thread periodic sync), `inspectCaches`/`clearCaches` (cache observability), `clearSessionData`/`clearBackgroundSyncQueue` (logout cleanup) — the React-free `tempest-react-sdk/sw` subpath is ideal for bundling into your own `sw.ts` |
|
|
325
325
|
| `editor` _(subpath `tempest-react-sdk/editor`, opt. peers `@tiptap/react`+`@tiptap/starter-kit`)_ | `RichTextEditor` — themed tiptap v3 rich-text editor with toolbar (tiptap externalized; install it only if you use the editor) |
|
|
326
326
|
| `vision` _(subpath `tempest-react-sdk/vision`, opt. peer `onnxruntime-web`)_ | `Classifier`, `Detector`, `Segmenter`, `DetectClassify` (fused detect→classify pipeline, optionally with a segmentation stage) + `warmup()`, FP16 models, `cache` (Cache Storage), video/`VideoFrame` inputs, `raiseOnEmpty`/`NoDetectionsError`, `LetterboxPipeline`, `readFusionSpec` and results/labels/preprocess — on-device ONNX vision inference, vendored from `@mauriciobenjamin700/ort-vision-sdk-web` (onnxruntime-web externalized; install it only if you use vision). Plus browser capture primitives: `useCameraStream` (rear-camera `getUserMedia` + classified errors + `retry()`), `computeImageLuminance`/`isLuminanceAcceptable`/`LowLuminanceError`/`LUMINANCE_SAMPLE_MAX_EDGE` (BT.709 brightness), `useLiveLuminance` (live rAF sampling) |
|
|
327
327
|
| `tabular` _(subpath `tempest-react-sdk/tabular`, opt. peer `onnxruntime-web`)_ | `TabularPredictor`, `useTabularPredictor` — scikit-learn models exported by `tempest-fastapi-sdk`'s `export_sklearn_to_onnx`, running in the browser with no inference server. Offline by design: `fetchModelBytes`/`isModelCached`/`cacheModelBytes`/`clearModelCache` (Cache Storage, cache-first) plus `configureOrtAssets`/`ortAssetUrls`/`ORT_WASM_ASSETS` for the runtime's WebAssembly binaries, which ONNX Runtime Web fetches at session creation. Import the default `onnxruntime-web` entry — the WebGPU build has no `ai.onnx.ml` kernels |
|
|
@@ -778,6 +778,37 @@ useAuthStore.getState().logout();
|
|
|
778
778
|
|
|
779
779
|
The store exposes `user`, `token`, `isAuthenticated`, `setSession`, `setUser`, `setToken`, and `logout`. `isAuthenticated` is derived from `token` and rehydrates correctly after page reload.
|
|
780
780
|
|
|
781
|
+
### Auth screens recipe
|
|
782
|
+
|
|
783
|
+
The sign-in screen without hand-building the layout or the form: `AuthPage` is the shell (colored top + rounded sheet below 768 px, 50/50 brand panel and form above it, or a centered `card`), and `LoginForm` / `SignUpForm` / `ForgotPasswordForm` / `ResetPasswordForm` are generated from a list of fields and a zod schema. A 422 with `fields` lands on the matching inputs; any other error shows an `Alert`.
|
|
784
|
+
|
|
785
|
+
```tsx
|
|
786
|
+
import { AuthPage, LoginForm, createApiClient, useNavigate } from "tempest-react-sdk";
|
|
787
|
+
|
|
788
|
+
const api = createApiClient({ baseURL: "/api" });
|
|
789
|
+
|
|
790
|
+
export function LoginScreen() {
|
|
791
|
+
const navigate = useNavigate();
|
|
792
|
+
return (
|
|
793
|
+
<AuthPage
|
|
794
|
+
brand={<img src="/logo.svg" alt="Acme" width={96} height={96} />}
|
|
795
|
+
back="/"
|
|
796
|
+
title="Bem-vindo!"
|
|
797
|
+
>
|
|
798
|
+
<LoginForm
|
|
799
|
+
onSubmit={async (values) => {
|
|
800
|
+
await api.post("/auth/login", { body: values });
|
|
801
|
+
navigate("/", { replace: true });
|
|
802
|
+
}}
|
|
803
|
+
links={{ forgotPassword: "/recuperar-senha", signUp: "/cadastro" }}
|
|
804
|
+
/>
|
|
805
|
+
</AuthPage>
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
```
|
|
809
|
+
|
|
810
|
+
Full guide: [Auth screens](https://mauriciobenjamin700.github.io/tempest-react-sdk/en/components/auth-screens/) · [Telas de autenticação](https://mauriciobenjamin700.github.io/tempest-react-sdk/components/auth-screens/).
|
|
811
|
+
|
|
781
812
|
### Route guard recipe
|
|
782
813
|
|
|
783
814
|
```tsx
|
|
@@ -15,10 +15,22 @@ import { fileURLToPath } from "node:url";
|
|
|
15
15
|
|
|
16
16
|
import { ESLint } from "eslint";
|
|
17
17
|
import ts from "typescript";
|
|
18
|
-
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
18
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
19
19
|
|
|
20
20
|
import { componentFiles, normalizeComponentName } from "./generate.mjs";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Gives the tests in this file 60 s instead of Vitest's 5 s default.
|
|
24
|
+
*
|
|
25
|
+
* Several of them build a TypeScript program (and, some, an ESLint instance)
|
|
26
|
+
* in-process over the generated files. That takes 3-5 s on an idle machine,
|
|
27
|
+
* right at the default, so a busy one — another suite, a model training — failed
|
|
28
|
+
* them by timeout while the code was correct: 5.1-5.7 s measured in three
|
|
29
|
+
* `make check` runs on 2026-11-01, load average between 21 and 48.
|
|
30
|
+
* `test/docs-guard.test.ts` gives its compile 120 s for the same reason.
|
|
31
|
+
*/
|
|
32
|
+
vi.setConfig({ testTimeout: 60_000 });
|
|
33
|
+
|
|
22
34
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
23
35
|
const REPO = resolve(HERE, "..", "..", "..");
|
|
24
36
|
const CLI = join(REPO, "bin", "tempest.mjs");
|
|
@@ -4,11 +4,23 @@ import { fileURLToPath } from "node:url";
|
|
|
4
4
|
import vm from "node:vm";
|
|
5
5
|
|
|
6
6
|
import ts from "typescript";
|
|
7
|
-
import { describe, expect, it } from "vitest";
|
|
7
|
+
import { describe, expect, it, vi } from "vitest";
|
|
8
8
|
|
|
9
9
|
import { encodeIdSegment } from "../../../src/data";
|
|
10
10
|
import { generate } from "./generate.mjs";
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Gives the tests in this file 60 s instead of Vitest's 5 s default.
|
|
14
|
+
*
|
|
15
|
+
* Several of them build a TypeScript program (and, some, an ESLint instance)
|
|
16
|
+
* in-process over the generated files. That takes 3-5 s on an idle machine,
|
|
17
|
+
* right at the default, so a busy one — another suite, a model training — failed
|
|
18
|
+
* them by timeout while the code was correct: 5.1-5.7 s measured in three
|
|
19
|
+
* `make check` runs on 2026-11-01, load average between 21 and 48.
|
|
20
|
+
* `test/docs-guard.test.ts` gives its compile 120 s for the same reason.
|
|
21
|
+
*/
|
|
22
|
+
vi.setConfig({ testTimeout: 60_000 });
|
|
23
|
+
|
|
12
24
|
const require = createRequire(import.meta.url);
|
|
13
25
|
/**
|
|
14
26
|
* Transpile one generated TypeScript file and run it in a fresh VM context.
|
|
@@ -84,6 +96,8 @@ function spec(paths, schemas = {}) {
|
|
|
84
96
|
}
|
|
85
97
|
|
|
86
98
|
const OK = { 200: { description: "ok" } };
|
|
99
|
+
const schemaRef = (name) => ({ $ref: `#/components/schemas/${name}` });
|
|
100
|
+
const json = (schema) => ({ content: { "application/json": { schema } } });
|
|
87
101
|
|
|
88
102
|
describe("generate — hostile spec text never becomes code (#434)", () => {
|
|
89
103
|
it("a tag that closes the class doc comment stays inside it", async () => {
|
|
@@ -233,12 +247,45 @@ describe("generate — hostile spec text never becomes code (#434)", () => {
|
|
|
233
247
|
await runGroup(files, "t");
|
|
234
248
|
});
|
|
235
249
|
|
|
250
|
+
/**
|
|
251
|
+
* Rewritten for #516: this used to pass with `spec({}, …)`, two schemas no
|
|
252
|
+
* route reaches. The check now runs over the schemas a group emits, so the
|
|
253
|
+
* pair has to be referenced to collide — and still is refused, naming both.
|
|
254
|
+
*/
|
|
236
255
|
it("two schema names that map to the same identifier are refused, naming both", () => {
|
|
256
|
+
const both = {
|
|
257
|
+
200: json({ anyOf: [schemaRef("User-Out"), schemaRef("user.Out")] }),
|
|
258
|
+
};
|
|
237
259
|
expect(() =>
|
|
238
|
-
generate(
|
|
260
|
+
generate(
|
|
261
|
+
spec(
|
|
262
|
+
{ "/a": { get: { tags: ["t"], responses: both } } },
|
|
263
|
+
{ "User-Out": { type: "object" }, "user.Out": { type: "object" } },
|
|
264
|
+
),
|
|
265
|
+
),
|
|
239
266
|
).toThrow(/"User-Out" and "user\.Out" both map to the TypeScript identifier "UserOut"/);
|
|
240
267
|
});
|
|
241
268
|
|
|
269
|
+
it("schema names no route reaches are neither emitted nor checked, so an orphan cannot abort the run (#516)", () => {
|
|
270
|
+
const { files } = generate(
|
|
271
|
+
spec(
|
|
272
|
+
{
|
|
273
|
+
"/a": {
|
|
274
|
+
get: { tags: ["t"], responses: { 200: json(schemaRef("MediaType")) } },
|
|
275
|
+
},
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
MediaType: { type: "string", enum: ["image/png"] },
|
|
279
|
+
"Media-Type": { type: "string", enum: ["image/png", "image/webp"] },
|
|
280
|
+
"***": { type: "object" },
|
|
281
|
+
},
|
|
282
|
+
),
|
|
283
|
+
);
|
|
284
|
+
expect(files["t/types.ts"]).toContain("export type MediaType = ");
|
|
285
|
+
expect(files["t/schemas.ts"]).not.toContain("image/webp");
|
|
286
|
+
expect(files["t/types.ts"].match(/export type/g)).toHaveLength(1);
|
|
287
|
+
});
|
|
288
|
+
|
|
242
289
|
it("a path parameter that maps onto another method argument is refused", () => {
|
|
243
290
|
expect(() =>
|
|
244
291
|
generate(
|
|
@@ -258,10 +305,16 @@ describe("generate — hostile spec text never becomes code (#434)", () => {
|
|
|
258
305
|
).toThrow(/"user-id" and "userId" both map to the TypeScript identifier "userId"/);
|
|
259
306
|
});
|
|
260
307
|
|
|
308
|
+
/** Rewritten for #516 like the collision above: the schema is now referenced. */
|
|
261
309
|
it("a name with nothing to build an identifier from is refused", () => {
|
|
262
|
-
expect(() =>
|
|
263
|
-
|
|
264
|
-
|
|
310
|
+
expect(() =>
|
|
311
|
+
generate(
|
|
312
|
+
spec(
|
|
313
|
+
{ "/a": { get: { tags: ["t"], responses: { 200: json(schemaRef("***")) } } } },
|
|
314
|
+
{ "***": { type: "object" } },
|
|
315
|
+
),
|
|
316
|
+
),
|
|
317
|
+
).toThrow(/schema name "\*\*\*" has no letter or digit/);
|
|
265
318
|
});
|
|
266
319
|
|
|
267
320
|
it("a regex pattern with an escaped slash or a line break compiles to the same regex", () => {
|
|
@@ -50,12 +50,88 @@ function methodName(op, method, path) {
|
|
|
50
50
|
const CLASS_MEMBERS = ["constructor", "api"];
|
|
51
51
|
|
|
52
52
|
/**
|
|
53
|
-
* Names a generated method body uses besides its path parameters: the `body
|
|
54
|
-
* `params` arguments, the `S` schemas import
|
|
55
|
-
*
|
|
56
|
-
* `bodyParam`), so it neither
|
|
53
|
+
* Names a generated method body uses besides its path parameters: the `body`,
|
|
54
|
+
* `params` and `headers` arguments, the `S` schemas import, the `encodeIdSegment`
|
|
55
|
+
* import and the module-level `headerRecord` helper. A path parameter that maps to
|
|
56
|
+
* one of them becomes `<name>Param` (`{body}` → `bodyParam`), so it neither
|
|
57
|
+
* duplicates an argument nor shadows the import.
|
|
57
58
|
*/
|
|
58
|
-
const METHOD_LOCALS = new Set([
|
|
59
|
+
const METHOD_LOCALS = new Set([
|
|
60
|
+
"body",
|
|
61
|
+
"params",
|
|
62
|
+
"headers",
|
|
63
|
+
"S",
|
|
64
|
+
"encodeIdSegment",
|
|
65
|
+
"headerRecord",
|
|
66
|
+
]);
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Header parameter names OpenAPI 3 says a generator SHALL ignore: the client sets
|
|
70
|
+
* `Accept` and `Content-Type` from the request it builds and `Authorization` from
|
|
71
|
+
* its `getToken`, so a spec that also declares them must not open a second way to
|
|
72
|
+
* send them. Compared case-insensitively, as HTTP header names are.
|
|
73
|
+
*/
|
|
74
|
+
const IGNORED_HEADERS = new Set(["accept", "content-type", "authorization"]);
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Module-level helper a `service.ts` declares when one of its methods sends header
|
|
78
|
+
* parameters. `fetch` turns a header value of `undefined` into the literal text
|
|
79
|
+
* `"undefined"`, and `RequestOptions.headers` only takes strings, so an optional
|
|
80
|
+
* header left out is dropped and every other value goes through `String()`.
|
|
81
|
+
*/
|
|
82
|
+
const HEADER_RECORD_HELPER = [
|
|
83
|
+
"/** Header parameters as strings, without the ones left out (`fetch` would send `undefined` as text). */",
|
|
84
|
+
"function headerRecord(",
|
|
85
|
+
" values: Record<string, string | number | boolean | null | undefined>,",
|
|
86
|
+
"): Record<string, string> {",
|
|
87
|
+
" const out: Record<string, string> = {};",
|
|
88
|
+
" for (const [name, value] of Object.entries(values)) {",
|
|
89
|
+
" if (value !== undefined && value !== null) out[name] = String(value);",
|
|
90
|
+
" }",
|
|
91
|
+
" return out;",
|
|
92
|
+
"}",
|
|
93
|
+
].join("\n");
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Follow a `#/components/parameters/...` `$ref` chain to the parameter object.
|
|
97
|
+
*
|
|
98
|
+
* @param {object | undefined} param - A parameter object or a reference to one.
|
|
99
|
+
* @param {Record<string, object>} components - `components.parameters`.
|
|
100
|
+
* @returns {object | undefined} The parameter object, or `undefined` when the
|
|
101
|
+
* chain does not resolve.
|
|
102
|
+
*/
|
|
103
|
+
function resolveParameter(param, components) {
|
|
104
|
+
let node = param;
|
|
105
|
+
let guard = 0;
|
|
106
|
+
while (node && node.$ref && guard++ < 16) node = components[refName(node.$ref)];
|
|
107
|
+
return node && !node.$ref ? node : undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* The parameters that apply to one operation: the path item's `parameters`
|
|
112
|
+
* merged with the operation's, the operation winning on the same `(name, in)` —
|
|
113
|
+
* the rule OpenAPI 3 sets for the two levels.
|
|
114
|
+
*
|
|
115
|
+
* Before #509 only `operation.parameters` was read, so a `{id}` declared on the
|
|
116
|
+
* path item reached the URL literally (`"/items/{id}"`, measured), and a path item
|
|
117
|
+
* declaring one placeholder while the operation declared the other refused to
|
|
118
|
+
* generate. `$ref`s into `components.parameters` are resolved here too: they were
|
|
119
|
+
* dropped the same way, because a reference object has no `in`.
|
|
120
|
+
*
|
|
121
|
+
* @param {object} item - The path item.
|
|
122
|
+
* @param {object} op - The operation.
|
|
123
|
+
* @param {Record<string, object>} components - `components.parameters`.
|
|
124
|
+
* @returns {object[]} The effective parameter objects, path-item order first.
|
|
125
|
+
*/
|
|
126
|
+
function operationParameters(item, op, components) {
|
|
127
|
+
const merged = new Map();
|
|
128
|
+
for (const raw of [...(item.parameters ?? []), ...(op.parameters ?? [])]) {
|
|
129
|
+
const param = resolveParameter(raw, components);
|
|
130
|
+
if (!param || typeof param.name !== "string" || typeof param.in !== "string") continue;
|
|
131
|
+
merged.set(`${param.in}\u0000${param.name}`, param);
|
|
132
|
+
}
|
|
133
|
+
return [...merged.values()];
|
|
134
|
+
}
|
|
59
135
|
|
|
60
136
|
/**
|
|
61
137
|
* Whether a path parameter's schema only ever produces a `string` or a `number` —
|
|
@@ -208,16 +284,17 @@ function isQueryScalar(schema, schemas, depth = 0) {
|
|
|
208
284
|
}
|
|
209
285
|
|
|
210
286
|
/**
|
|
211
|
-
* TS type for a query-param value.
|
|
212
|
-
* client
|
|
213
|
-
* (objects, arrays) is
|
|
287
|
+
* TS type for a query- or header-param value. Both serialize to strings, so the
|
|
288
|
+
* client's `params` and the generated `headerRecord` only accept
|
|
289
|
+
* `string | number | boolean | null`. Anything richer (objects, arrays) is
|
|
290
|
+
* narrowed to `string` to stay assignable.
|
|
214
291
|
*
|
|
215
292
|
* @param {object | undefined} schema - The parameter's schema.
|
|
216
293
|
* @param {Record<string, object>} schemas - `components.schemas`.
|
|
217
294
|
* @param {Set<string>} refs - Collects the schemas the type names.
|
|
218
295
|
* @returns {string} The TypeScript type expression.
|
|
219
296
|
*/
|
|
220
|
-
function
|
|
297
|
+
function scalarParamType(schema, schemas, refs) {
|
|
221
298
|
const node = schema ?? { type: "string" };
|
|
222
299
|
return isQueryScalar(node, schemas) ? tsType(node, refs) : "string";
|
|
223
300
|
}
|
|
@@ -343,16 +420,25 @@ function compareNames(a, b) {
|
|
|
343
420
|
* template's ESLint enforces, so the output passes its lint and `tsc` with
|
|
344
421
|
* `noUnusedLocals` as generated (#504, #505).
|
|
345
422
|
*
|
|
423
|
+
* A component schema no route reaches is never emitted — FastAPI publishes an
|
|
424
|
+
* unreferenced enum for every `Enum`-annotated field (`ImageMediaType`, #516),
|
|
425
|
+
* and it lands in no group. Its name is not checked either: the identifier
|
|
426
|
+
* collision check runs per group over the schemas that group emits, so an orphan
|
|
427
|
+
* named `Media-Type` next to a referenced `MediaType` no longer aborts the whole
|
|
428
|
+
* run for a type nobody would see.
|
|
429
|
+
*
|
|
430
|
+
* Parameters are read from the path item and the operation, merged by
|
|
431
|
+
* `(name, in)` (see {@link operationParameters}); `in: "cookie"` is skipped,
|
|
432
|
+
* because a browser `fetch` cannot set `Cookie` — the browser sends its own
|
|
433
|
+
* cookies with `credentials`, which is how a cookie parameter reaches the server.
|
|
434
|
+
*
|
|
346
435
|
* @param {object} doc - The parsed OpenAPI 3.x spec.
|
|
347
436
|
* @returns {{ files: Record<string, string>, tags: string[] }}
|
|
348
437
|
*/
|
|
349
438
|
export function generate(doc) {
|
|
350
439
|
const schemas = doc.components?.schemas ?? {};
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"schema names",
|
|
354
|
-
);
|
|
355
|
-
const groups = new Map(); // slug → { tag, ops: [...] }
|
|
440
|
+
const parameterComponents = doc.components?.parameters ?? {};
|
|
441
|
+
const groups = new Map();
|
|
356
442
|
|
|
357
443
|
for (const [path, item] of Object.entries(doc.paths ?? {})) {
|
|
358
444
|
for (const method of HTTP_METHODS) {
|
|
@@ -361,7 +447,8 @@ export function generate(doc) {
|
|
|
361
447
|
const tag = op.tags?.[0] ?? "default";
|
|
362
448
|
const slug = tagSlug(tag);
|
|
363
449
|
if (!groups.has(slug)) groups.set(slug, { tag, slug, ops: [] });
|
|
364
|
-
|
|
450
|
+
const params = operationParameters(item, op, parameterComponents);
|
|
451
|
+
groups.get(slug).ops.push({ method, path, op, params });
|
|
365
452
|
}
|
|
366
453
|
}
|
|
367
454
|
|
|
@@ -378,9 +465,9 @@ export function generate(doc) {
|
|
|
378
465
|
const Class = `${slugIdent(pascal(slug))}Service`;
|
|
379
466
|
|
|
380
467
|
const usedNames = new Set(CLASS_MEMBERS);
|
|
381
|
-
const signature = { refs: new Set(), validates: false };
|
|
382
|
-
const methods = ops.map(({ method, path, op }) =>
|
|
383
|
-
emitMethod(method, path, op, schemas, usedNames, signature),
|
|
468
|
+
const signature = { refs: new Set(), validates: false, sendsHeaders: false };
|
|
469
|
+
const methods = ops.map(({ method, path, op, params }) =>
|
|
470
|
+
emitMethod(method, path, op, params, schemas, usedNames, signature),
|
|
384
471
|
);
|
|
385
472
|
|
|
386
473
|
const used = new Set();
|
|
@@ -392,6 +479,10 @@ export function generate(doc) {
|
|
|
392
479
|
}
|
|
393
480
|
for (const name of signature.refs) collectRefs({ $ref: name }, schemas, used);
|
|
394
481
|
const { sorted, cyclic } = topoSort(used, schemas);
|
|
482
|
+
assertNoCollision(
|
|
483
|
+
sorted.map((n) => [n, typeName(n)]),
|
|
484
|
+
"schema names",
|
|
485
|
+
);
|
|
395
486
|
for (const name of sorted) assertPatterns(schemas[name], name);
|
|
396
487
|
|
|
397
488
|
const schemaLines = sorted.map((name) => {
|
|
@@ -433,6 +524,7 @@ export function generate(doc) {
|
|
|
433
524
|
.filter((group) => group.length)
|
|
434
525
|
.map((group) => group.join("\n"))
|
|
435
526
|
.join("\n\n") +
|
|
527
|
+
(signature.sendsHeaders ? `\n\n${HEADER_RECORD_HELPER}` : "") +
|
|
436
528
|
`\n\n/** Generated service for the "${commentText(tag)}" routes. Inject an ApiClient (createApiClient). */\n` +
|
|
437
529
|
`export class ${Class} {\n` +
|
|
438
530
|
` constructor(private readonly api: ApiClient) {}\n\n` +
|
|
@@ -461,16 +553,27 @@ export function generate(doc) {
|
|
|
461
553
|
* pagination envelope maps to `OffsetPage<T>` / `CursorPage<T>`, and a body typed
|
|
462
554
|
* by a `$ref` is validated with its Zod schema before the call.
|
|
463
555
|
*
|
|
556
|
+
* Arguments come in a fixed order: path parameters, `body`, `params` (query),
|
|
557
|
+
* `headers`. Header parameters are typed like query parameters and sent through
|
|
558
|
+
* the request's `headers`, via `headerRecord`; the `headers` argument defaults to
|
|
559
|
+
* `{}` when none of them is required, so a spec gaining an optional header does
|
|
560
|
+
* not break the call sites. `Accept`, `Content-Type` and `Authorization` are left
|
|
561
|
+
* out ({@link IGNORED_HEADERS}), and so is every `in: "cookie"` parameter: a
|
|
562
|
+
* browser `fetch` cannot set `Cookie` (forbidden header name), the cookie jar
|
|
563
|
+
* sends it with `credentials`.
|
|
564
|
+
*
|
|
464
565
|
* @param {string} method - The HTTP method, lower case.
|
|
465
566
|
* @param {string} path - The OpenAPI path template.
|
|
466
567
|
* @param {object} op - The operation object.
|
|
568
|
+
* @param {object[]} parameters - The effective parameters, from {@link operationParameters}.
|
|
467
569
|
* @param {Record<string, object>} schemas - `components.schemas`.
|
|
468
570
|
* @param {Set<string>} usedNames - Member names already taken in the class.
|
|
469
|
-
* @param {{ refs: Set<string>, validates: boolean }} signature -
|
|
470
|
-
* component schemas the emitted types name,
|
|
571
|
+
* @param {{ refs: Set<string>, validates: boolean, sendsHeaders: boolean }} signature -
|
|
572
|
+
* Collects the component schemas the emitted types name, whether `S` was used
|
|
573
|
+
* and whether `headerRecord` was.
|
|
471
574
|
* @returns {string} The method source.
|
|
472
575
|
*/
|
|
473
|
-
function emitMethod(method, path, op, schemas, usedNames, signature) {
|
|
576
|
+
function emitMethod(method, path, op, parameters, schemas, usedNames, signature) {
|
|
474
577
|
let name = methodName(op, method, path);
|
|
475
578
|
if (usedNames.has(name)) {
|
|
476
579
|
let n = 2;
|
|
@@ -478,7 +581,11 @@ function emitMethod(method, path, op, schemas, usedNames, signature) {
|
|
|
478
581
|
name = `${name}${n}`;
|
|
479
582
|
}
|
|
480
583
|
usedNames.add(name);
|
|
481
|
-
const pathParams =
|
|
584
|
+
const pathParams = parameters.filter((p) => p.in === "path");
|
|
585
|
+
const queryParams = parameters.filter((p) => p.in === "query");
|
|
586
|
+
const headerParams = parameters.filter(
|
|
587
|
+
(p) => p.in === "header" && !IGNORED_HEADERS.has(p.name.toLowerCase()),
|
|
588
|
+
);
|
|
482
589
|
const where = `${method.toUpperCase()} ${path}`;
|
|
483
590
|
const paramId = (name) => {
|
|
484
591
|
const id = toIdentifier(name, "camel", `path parameter in ${where}`);
|
|
@@ -496,13 +603,11 @@ function emitMethod(method, path, op, schemas, usedNames, signature) {
|
|
|
496
603
|
[
|
|
497
604
|
...[...paramIds],
|
|
498
605
|
...(bodySchema(op) ? [["the request body argument", "body"]] : []),
|
|
499
|
-
...(
|
|
500
|
-
|
|
501
|
-
: []),
|
|
606
|
+
...(queryParams.length ? [["the query argument", "params"]] : []),
|
|
607
|
+
...(headerParams.length ? [["the headers argument", "headers"]] : []),
|
|
502
608
|
],
|
|
503
609
|
`method arguments of ${where}`,
|
|
504
610
|
);
|
|
505
|
-
const queryParams = (op.parameters ?? []).filter((p) => p.in === "query");
|
|
506
611
|
const body = bodySchema(op);
|
|
507
612
|
const resp = successSchema(op);
|
|
508
613
|
const page = resp ? detectPage(resp, schemas, signature.refs) : null;
|
|
@@ -512,6 +617,14 @@ function emitMethod(method, path, op, schemas, usedNames, signature) {
|
|
|
512
617
|
? tsType(resp, signature.refs)
|
|
513
618
|
: "void";
|
|
514
619
|
|
|
620
|
+
const fields = (list) =>
|
|
621
|
+
list
|
|
622
|
+
.map(
|
|
623
|
+
(p) =>
|
|
624
|
+
`${JSON.stringify(p.name)}${p.required ? "" : "?"}: ${scalarParamType(p.schema, schemas, signature.refs)}`,
|
|
625
|
+
)
|
|
626
|
+
.join("; ");
|
|
627
|
+
|
|
515
628
|
const args = [];
|
|
516
629
|
for (const p of pathParams) {
|
|
517
630
|
args.push(
|
|
@@ -519,14 +632,11 @@ function emitMethod(method, path, op, schemas, usedNames, signature) {
|
|
|
519
632
|
);
|
|
520
633
|
}
|
|
521
634
|
if (body) args.push(`body: ${tsType(body, signature.refs)}`);
|
|
522
|
-
if (queryParams.length) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
)
|
|
528
|
-
.join("; ");
|
|
529
|
-
args.push(`params: { ${q} }`);
|
|
635
|
+
if (queryParams.length) args.push(`params: { ${fields(queryParams)} }`);
|
|
636
|
+
if (headerParams.length) {
|
|
637
|
+
const optional = headerParams.every((p) => !p.required);
|
|
638
|
+
args.push(`headers: { ${fields(headerParams)} }${optional ? " = {}" : ""}`);
|
|
639
|
+
signature.sendsHeaders = true;
|
|
530
640
|
}
|
|
531
641
|
|
|
532
642
|
const url = urlTemplate(path, segmentArgs);
|
|
@@ -534,6 +644,7 @@ function emitMethod(method, path, op, schemas, usedNames, signature) {
|
|
|
534
644
|
const callOpts = [];
|
|
535
645
|
if (body) callOpts.push("body");
|
|
536
646
|
if (queryParams.length) callOpts.push("params");
|
|
647
|
+
if (headerParams.length) callOpts.push("headers: headerRecord(headers)");
|
|
537
648
|
const optsArg = callOpts.length ? `, { ${callOpts.join(", ")} }` : "";
|
|
538
649
|
|
|
539
650
|
let validation = "";
|