| {{!-- --- | ||
| behavior: scaffold | ||
| target: {{outDir}}/plugins | ||
| filter: | ||
| {{#each pluginNames}} | ||
| - {{this}}.hbs | ||
| {{/each}} | ||
| --- --}} | ||
| import type { ApiClient } from "{{#if importAlias}}{{importAlias}}/core/api-client{{else}}{{relPath (concat outDir "/plugins") coreOut}}/api-client{{/if}}"; | ||
| import type { ApiPlugin } from "{{#if importAlias}}{{importAlias}}/core{{else}}{{relPath (concat outDir "/plugins") coreOut}}{{/if}}/types"; | ||
| export class CurrencyManager { | ||
| private currency: string | null = null; | ||
| public setCurrency(currency: string | null) { | ||
| this.currency = currency; | ||
| } | ||
| public getCurrency(): string | null { | ||
| return this.currency; | ||
| } | ||
| } | ||
| declare module "{{#if importAlias}}{{importAlias}}/core/types{{else}}{{relPath (concat outDir "/plugins") coreOut}}/types{{/if}}" { | ||
| interface PluginRegistry { | ||
| currency: CurrencyManager; | ||
| } | ||
| } | ||
| export function installCurrency(client: ApiClient) { | ||
| const manager = new CurrencyManager(); | ||
| const plugin: ApiPlugin = { | ||
| name: "currency", | ||
| onInit: (c) => { | ||
| c.use("currency", manager); | ||
| }, | ||
| onRequest: async (config) => { | ||
| const headers = new Headers(config.headers); | ||
| const currency = manager.getCurrency(); | ||
| if (currency && !headers.has("X-Currency")) { | ||
| headers.set("X-Currency", currency); | ||
| } | ||
| return { ...config, headers }; | ||
| } | ||
| }; | ||
| client.use(plugin); | ||
| } |
| {{!-- --- | ||
| behavior: scaffold | ||
| target: {{outDir}}/plugins | ||
| filter: | ||
| {{#each pluginNames}} | ||
| - {{this}}.hbs | ||
| {{/each}} | ||
| --- --}} | ||
| import type { ApiClient } from "{{#if importAlias}}{{importAlias}}/core/api-client{{else}}{{relPath (concat outDir "/plugins") coreOut}}/api-client{{/if}}"; | ||
| import type { ApiPlugin } from "{{#if importAlias}}{{importAlias}}/core{{else}}{{relPath (concat outDir "/plugins") coreOut}}{{/if}}/types"; | ||
| export class LocaleManager { | ||
| private locale: string | null = null; | ||
| public setLocale(locale: string | null) { | ||
| this.locale = locale; | ||
| } | ||
| public getLocale(): string | null { | ||
| return this.locale; | ||
| } | ||
| } | ||
| declare module "{{#if importAlias}}{{importAlias}}/core/types{{else}}{{relPath (concat outDir "/plugins") coreOut}}/types{{/if}}" { | ||
| interface PluginRegistry { | ||
| locale: LocaleManager; | ||
| } | ||
| } | ||
| export function installLocale(client: ApiClient) { | ||
| const manager = new LocaleManager(); | ||
| if (typeof window !== "undefined" && typeof navigator !== "undefined") { | ||
| manager.setLocale(navigator.language); | ||
| } | ||
| const plugin: ApiPlugin = { | ||
| name: "locale", | ||
| onInit: (c) => { | ||
| c.use("locale", manager); | ||
| }, | ||
| onRequest: async (config) => { | ||
| const headers = new Headers(config.headers); | ||
| const locale = manager.getLocale(); | ||
| if (locale && !headers.has("Accept-Language")) { | ||
| headers.set("Accept-Language", locale); | ||
| } | ||
| return { ...config, headers }; | ||
| } | ||
| }; | ||
| client.use(plugin); | ||
| } |
| {{!-- --- | ||
| behavior: scaffold | ||
| target: {{outDir}}/plugins | ||
| filter: | ||
| {{#each pluginNames}} | ||
| - {{this}}.hbs | ||
| {{/each}} | ||
| --- --}} | ||
| import type { ApiClient } from "{{#if importAlias}}{{importAlias}}/core/api-client{{else}}{{relPath (concat outDir "/plugins") coreOut}}/api-client{{/if}}"; | ||
| import type { ApiPlugin } from "{{#if importAlias}}{{importAlias}}/core{{else}}{{relPath (concat outDir "/plugins") coreOut}}{{/if}}/types"; | ||
| export function installTimezone(client: ApiClient) { | ||
| const timezonePlugin: ApiPlugin = { | ||
| name: "timezone", | ||
| onRequest: async (config) => { | ||
| const headers = new Headers(config.headers); | ||
| if (!headers.has("X-Timezone")) { | ||
| try { | ||
| const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; | ||
| if (tz) { | ||
| headers.set("X-Timezone", tz); | ||
| } | ||
| } catch (e) { | ||
| // Fallback if Intl is not supported | ||
| } | ||
| } | ||
| return { ...config, headers }; | ||
| } | ||
| }; | ||
| client.use(timezonePlugin); | ||
| } |
+8
-4
| { | ||
| "name": "specshot", | ||
| "version": "3.0.3", | ||
| "version": "3.0.4", | ||
| "description": "Fire an OpenAPI spec, get strictly-typed TypeScript code and a robust API mock server — zero-dependency, Zod-validated, and highly customizable.", | ||
@@ -32,3 +32,4 @@ "type": "module", | ||
| "test:coverage": "vitest run --coverage", | ||
| "lint": "npm run lint:types && npm run lint:format", | ||
| "lint": "npm run lint:types && npm run lint:js && npm run lint:format", | ||
| "lint:js": "eslint 'src/**/*.ts'", | ||
| "lint:types": "tsc --noEmit", | ||
@@ -82,8 +83,11 @@ "lint:format": "prettier --check .", | ||
| "@types/node": "^25.9.2", | ||
| "@typescript-eslint/parser": "^5.0.1", | ||
| "eslint": "^8.52.0", | ||
| "husky": "^9.1.7", | ||
| "prettier": "^3.9.4", | ||
| "prettier": "^3.1.0", | ||
| "prettier-eslint": "^16.1.2", | ||
| "tsup": "^8.5.1", | ||
| "typescript": "^6.0.3", | ||
| "typescript": "^5.6.3", | ||
| "vitest": "^1.6.0" | ||
| } | ||
| } |
@@ -78,2 +78,5 @@ # Class Preset for Specshot | ||
| - **`"circuit-breaker"`**: Scaffolds circuit breaker logic to temporarily stop calling endpoints if they fail repeatedly. | ||
| - **`"timezone"`**: Automatically attaches an `X-Timezone` header based on the user's local timezone. | ||
| - **`"locale"`**: Manages and injects the `Accept-Language` header automatically. | ||
| - **`"currency"`**: Manages and injects the `X-Currency` header automatically. | ||
@@ -175,4 +178,20 @@ #### `toastLibrary` | ||
| ### 4. The Global Toast Plugin | ||
| ### 4. Locale and Currency Plugins | ||
| If you enable the `"locale"` or `"currency"` plugins, they automatically inject `Accept-Language` and `X-Currency` headers into your requests. You can globally set them using their respective managers. | ||
| ```typescript | ||
| import { api } from "@/lib/api"; | ||
| const currencyManager = api.plugin("currency"); | ||
| currencyManager.setCurrency("THB"); | ||
| // Next request will include `X-Currency: THB` | ||
| const localeManager = api.plugin("locale"); | ||
| localeManager.setLocale("th-TH"); | ||
| // Next request will include `Accept-Language: th-TH` | ||
| ``` | ||
| ### 5. The Global Toast Plugin | ||
| If you selected a Toast library, the client will automatically trigger a UI toast whenever an API request fails. The message displayed will be the one extracted by your `setErrorExtractor`. | ||
@@ -190,3 +209,3 @@ | ||
| ### 5. API Request Cancellation | ||
| ### 6. API Request Cancellation | ||
@@ -224,3 +243,3 @@ Every request made by the client returns a `CancelablePromise`. This means you can easily abort an in-flight request by calling `.cancel()` on the returned promise. | ||
| ### 6. Writing Custom Plugins | ||
| ### 7. Writing Custom Plugins | ||
@@ -254,3 +273,3 @@ You can easily extend the client by writing your own interceptors. | ||
| ### 6. Using React Hooks (SWR / React Query) | ||
| ### 8. Using React Hooks (SWR / React Query) | ||
@@ -257,0 +276,0 @@ If hook generation is enabled, Specshot generates ready-to-use hooks for all `GET` endpoints. These hooks handle caching, deduplication, and loading states automatically. |
@@ -11,3 +11,11 @@ { | ||
| "type": "string", | ||
| "enum": ["bearer", "logger", "request-id", "circuit-breaker"] | ||
| "enum": [ | ||
| "bearer", | ||
| "logger", | ||
| "request-id", | ||
| "circuit-breaker", | ||
| "timezone", | ||
| "locale", | ||
| "currency" | ||
| ] | ||
| }, | ||
@@ -14,0 +22,0 @@ "default": [], |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
14598863
0.03%51
6.25%1248
0.65%13
-7.14%11
37.5%