svelte-reflector 1.3.11 → 2.1.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 CHANGED
@@ -1,395 +1,477 @@
1
- # Svelte Reflector
2
-
3
- **Turn your OpenAPI into a first-class Svelte 5 DX.**
4
-
5
- Svelte Reflector is a **developer-experience-first code generator** that converts OpenAPI specs into fully typed, reactive Svelte 5 modules — ready for production, forms included.
6
-
7
- [![npm version](https://img.shields.io/npm/v/svelte-reflector.svg)](https://www.npmjs.com/package/svelte-reflector)
8
- [![npm downloads](https://img.shields.io/npm/dm/svelte-reflector.svg)](https://www.npmjs.com/package/svelte-reflector)
9
- [![npm total downloads](https://img.shields.io/npm/dt/svelte-reflector.svg)](https://www.npmjs.com/package/svelte-reflector)
10
- [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/)
11
- [![Svelte](https://img.shields.io/badge/Svelte-5+-orange.svg)](https://svelte.dev/)
12
-
13
- ## Features
14
-
15
- - **Automatic Type Generation** - Generates TypeScript interfaces and classes from OpenAPI schemas
16
- - **Svelte 5 Runes Integration** - Uses `$state` and `$derived` for reactive state management
17
- - **Abstract Modules** - Generated modules are abstract classes, ready to be extended with custom logic
18
- - **Per-Module Schemas** - Each module gets its own schema file with only the types it needs (tree-shaking friendly)
19
- - **Form Handling** - Auto-generates form schemas with `BuildedInput<T>` wrappers and validation support
20
- - **Type-Safe API Calls** - Full TypeScript support for all API operations
21
- - **Query Parameter Sync** - `QueryBuilder` and `EnumQueryBuilder` keep state synced with URL searchParams
22
- - **Enum Support** - Auto-generates enum types and array enum query builders
23
- - **OpenAPI/Swagger Compatible** - Works with any backend that exposes OpenAPI specs
24
- - **Development Mode** - Smart regeneration based on environment
25
- - **Validation Ready** - Built-in support for custom field validators
26
- - **Vite Plugin** - Can be used as a Vite plugin for automatic generation on build
27
-
28
- ## Installation
29
-
30
- ```bash
31
- npm install svelte-reflector
32
- # or
33
- yarn add svelte-reflector
34
- # or
35
- pnpm add svelte-reflector
36
- ```
37
-
38
- > **Note:** `prettier` >= 3.0.0 is a required peer dependency. Make sure it's installed in your project.
39
-
40
- ## Quick Start
41
-
42
- ### 1. Configure Environment Variables
43
-
44
- Create a `.env` file in your project root:
45
-
46
- ```env
47
- # Required - Your backend URL
48
- BACKEND_URL=https://api.example.com/
49
- # or
50
- PUBLIC_BACKEND=https://api.example.com/
51
-
52
- # Optional - Environment (defaults to PROD)
53
- ENVIRONMENT=DEV
54
- # or
55
- VITE_ENVIRONMENT=DEV
56
- ```
57
-
58
- ### 2. Create Reflector Config (Optional)
59
-
60
- Create a `src/reflector.config.ts` to define custom validators:
61
-
62
- ```typescript
63
- export const validators = [
64
- {
65
- fields: ["email", "userEmail"],
66
- validator: "validateEmail",
67
- },
68
- {
69
- fields: ["phone", "mobile"],
70
- validator: "validatePhone",
71
- },
72
- ];
73
- ```
74
-
75
- Validators are resolved from `$lib/sanitizers/validateFormats` — you need to implement and export them in your project.
76
-
77
- ### 3. Configure API Import Path (Optional)
78
-
79
- Create a `reflector.json` in your project root to customize the API import path:
80
-
81
- ```json
82
- {
83
- "api": "$lib/api"
84
- }
85
- ```
86
-
87
- Defaults to `$lib/api` if not specified. This is the module that generated modules will import for making HTTP requests.
88
-
89
- ### 4. Run the Generator
90
-
91
- ```bash
92
- # Manual generation
93
- npx reflect
94
-
95
- # Or programmatically as a Vite plugin
96
- import { reflector } from "svelte-reflector";
97
- await reflector(true); // true = force generation
98
- ```
99
-
100
- ### 5. Use Generated Modules
101
-
102
- Generated modules are **abstract classes**. Extend them to add custom logic or simply to instantiate:
103
-
104
- ```typescript
105
- import { UserModule } from "$reflector/controllers/user/user.module.svelte";
106
- import type { User } from "$reflector/controllers/user/user.schema.svelte";
107
-
108
- // Extend the abstract module
109
- class UserService extends UserModule {}
110
-
111
- const userService = new UserService();
112
-
113
- // Access reactive state
114
- console.log(userService.loading); // $state<boolean>
115
- console.log(userService.list); // $state<User[]>
116
-
117
- // Call API methods
118
- await userService.listAll({
119
- behavior: {
120
- onSuccess: (response) => console.log(response),
121
- onError: (error) => console.error(error),
122
- },
123
- });
124
-
125
- // Work with forms
126
- const userForm = userService.forms.createUser;
127
- userForm.name.value = "John Doe";
128
- userForm.email.value = "john@example.com";
129
-
130
- // Submit form
131
- await userService.createUser();
132
- ```
133
-
134
- ## Generated Structure
135
-
136
- ```
137
- src/reflector/
138
- ├── controllers/
139
- │ └── user/
140
- │ ├── user.module.svelte.ts # Abstract API module with methods
141
- │ └── user.schema.svelte.ts # Schemas & types used by this module
142
- ├── reflector.svelte.ts # Core utilities (build, isFormValid, QueryBuilder, etc.)
143
- ├── fields.ts # Field name constants
144
- ├── enums.ts # Enum type definitions
145
- ├── mocked-params.svelte.ts # Mocked path parameters ($state)
146
- └── backup.json # Cached OpenAPI spec
147
- ```
148
-
149
- Each module gets its own schema file (`*.schema.svelte.ts`) containing only the schemas it uses, with transitive dependencies automatically resolved.
150
-
151
- ## Generated Module API
152
-
153
- Each generated module is an **abstract class** that provides:
154
-
155
- ### State Properties
156
-
157
- | Property | Type | Description |
158
- |----------|------|-------------|
159
- | `loading` | `$state<boolean>` | Request loading state |
160
- | `list` | `$state<T[]>` | List results (for list endpoints) |
161
- | `forms` | `$state<Record<string, T>>` | Form instances |
162
- | `querys` | `Querys` | Query parameter state (QueryBuilder instances) |
163
- | `headers` | `Headers` | Header state |
164
- | `paths` | `Paths` | Path parameter state |
165
-
166
- ### Methods
167
-
168
- ```typescript
169
- // List all items (GET with page parameter)
170
- async listAll(params?: { behavior?: Behavior }): Promise<T[]>
171
-
172
- // Get single entity (GET without page parameter)
173
- async get(params?: { behavior?: Behavior }): Promise<T>
174
-
175
- // Create/Update (POST/PUT/PATCH)
176
- async create(params?: { behavior?: Behavior }): Promise<T>
177
- async update(params?: { behavior?: Behavior }): Promise<T>
178
-
179
- // Delete (DELETE)
180
- async delete(params?: { behavior?: Behavior }): Promise<void>
181
-
182
- // Reset all state (protected)
183
- protected reset(): void
184
-
185
- // Clear forms (protected)
186
- protected clearForms(): void
187
- ```
188
-
189
- > `reset()` and `clearForms()` are `protected` — override them in your subclass if you need custom reset behavior.
190
-
191
- ### QueryBuilder
192
-
193
- Query parameters are wrapped in `QueryBuilder` instances that sync with URL searchParams:
194
-
195
- ```typescript
196
- // Single value query parameter
197
- const querys = module.querys;
198
- querys.status.update("active"); // Updates URL searchParam and internal state
199
-
200
- // Array enum query parameter
201
- const enumQuery = module.querys.roles; // EnumQueryBuilder<RoleType>
202
- enumQuery.selected = "admin";
203
- enumQuery.add(); // Adds to URL searchParams
204
- enumQuery.remove(0); // Removes from URL searchParams
205
- enumQuery.values; // $derived from URL - always in sync
206
- ```
207
-
208
- ## Configuration
209
-
210
- ### Environment Variables
211
-
212
- | Variable | Required | Description |
213
- |----------|----------|-------------|
214
- | `BACKEND_URL` | Yes* | Backend API URL |
215
- | `PUBLIC_BACKEND` | Yes* | Alternative to BACKEND_URL |
216
- | `ENVIRONMENT` | No | DEV/PROD (defaults to PROD) |
217
- | `VITE_ENVIRONMENT` | No | Vite-specific env var |
218
- | `NODE_ENV` | No | Node environment |
219
-
220
- \* At least one of `BACKEND_URL` or `PUBLIC_BACKEND` is required.
221
-
222
- ### Behavior Pattern
223
-
224
- All API methods accept a `Behavior` object for callbacks:
225
-
226
- ```typescript
227
- class Behavior<TSuccess, TError> {
228
- onSuccess?: (value: TSuccess) => Promise<void> | void;
229
- onError?: (error: TError) => Promise<void> | void;
230
- }
231
-
232
- // Usage
233
- await userService.createUser({
234
- behavior: {
235
- onSuccess: (user) => console.log("Created:", user),
236
- onError: (err) => console.error("Failed:", err),
237
- },
238
- });
239
- ```
240
-
241
- ### Form Validation
242
-
243
- Forms use `BuildedInput` class with validation:
244
-
245
- ```typescript
246
- class BuildedInput<T> {
247
- value: T; // Current value ($state)
248
- display: T; // Display value ($state)
249
- required: boolean; // Is field required
250
- placeholder: T; // Placeholder/example value
251
- readonly kind: 'builded';
252
- validator?: (v: T) => string | null; // Validation function
253
- validate(): string | null; // Run validation
254
- }
255
-
256
- // Check if all form fields are valid
257
- import { isFormValid } from "$reflector/reflector.svelte";
258
-
259
- if (isFormValid(userService.forms.createUser)) {
260
- await userService.createUser();
261
- }
262
- ```
263
-
264
- ## TypeScript Configuration
265
-
266
- Add path aliases to your `tsconfig.json`:
267
-
268
- ```json
269
- {
270
- "compilerOptions": {
271
- "paths": {
272
- "$reflector/*": ["./src/reflector/*"],
273
- "$lib/*": ["./src/lib/*"]
274
- }
275
- }
276
- }
277
- ```
278
-
279
- For Vite projects, also update `vite.config.ts`:
280
-
281
- ```typescript
282
- export default defineConfig({
283
- resolve: {
284
- alias: {
285
- $reflector: path.resolve("./src/reflector"),
286
- $lib: path.resolve("./src/lib"),
287
- },
288
- },
289
- });
290
- ```
291
-
292
- ## Workflow
293
-
294
- ### Development Mode
295
-
296
- In `ENVIRONMENT=DEV`:
297
- - Schemas are **NOT** auto-regenerated on build
298
- - Use `npx reflect` to manually regenerate
299
- - Faster builds, manual control
300
-
301
- ### Production Mode
302
-
303
- In `ENVIRONMENT=PROD`:
304
- - Schemas are auto-regenerated on each build
305
- - Fresh types from latest OpenAPI spec
306
- - Fallback to `backup.json` if backend is unavailable
307
-
308
- ## Advanced Usage
309
-
310
- ### Extending Abstract Modules
311
-
312
- Since modules are abstract, you can add custom logic:
313
-
314
- ```typescript
315
- import { UserModule } from "$reflector/controllers/user/user.module.svelte";
316
-
317
- class UserService extends UserModule {
318
- // Add custom computed state
319
- get activeUsers() {
320
- return this.list.filter(u => u.active);
321
- }
322
-
323
- // Override protected methods for custom behavior
324
- protected override clearForms() {
325
- super.clearForms();
326
- // custom cleanup logic
327
- }
328
-
329
- // Add custom methods
330
- async fetchAndFilter(status: string) {
331
- this.querys.status.update(status);
332
- await this.listAll();
333
- }
334
- }
335
- ```
336
-
337
- ### Manual Schema Access
338
-
339
- ```typescript
340
- import { User } from "$reflector/controllers/user/user.schema.svelte";
341
-
342
- // Create instance
343
- const user = new User({ name: "John", email: "john@example.com" });
344
-
345
- // Get data bundle
346
- const data = user.bundle(); // { name: "John", email: "john@example.com" }
347
- ```
348
-
349
- ### Batch Query Updates
350
-
351
- ```typescript
352
- import { setQueryGroup } from "$reflector/reflector.svelte";
353
-
354
- // Update multiple query params at once
355
- setQueryGroup([
356
- { key: "page", value: 1 },
357
- { key: "status", value: "active" },
358
- { key: "roles", value: ["admin", "editor"] }, // Array params supported
359
- ]);
360
- ```
361
-
362
- ## Troubleshooting
363
-
364
- ### "BACKEND_URL vazio" Error
365
-
366
- Ensure you have set `BACKEND_URL` or `PUBLIC_BACKEND` in your `.env` file.
367
-
368
- ### Schemas Not Updating
369
-
370
- In DEV mode, run `npx reflect` manually. Check that your backend's OpenAPI spec is accessible at `{BACKEND_URL}openapi.json`.
371
-
372
- ### Type Errors After Generation
373
-
374
- 1. Restart your TypeScript language server
375
- 2. Check path aliases in `tsconfig.json`
376
- 3. Ensure `$reflector/*` alias is configured
377
-
378
- ## License
379
-
380
- MIT License - see [LICENSE](LICENSE) for details.
381
-
382
- ## Contributing
383
-
384
- Contributions are welcome! Please feel free to submit a Pull Request.
385
-
386
- ## Links
387
-
388
- - [npm](https://www.npmjs.com/package/svelte-reflector)
389
- - [GitHub](https://github.com/aleleppy/reflector)
390
- - [Svelte](https://svelte.dev/)
391
- - [OpenAPI Specification](https://swagger.io/specification/)
392
-
393
- ---
394
-
395
- Built with by the Pinaculo Digital team.
1
+ # Svelte Reflector
2
+
3
+ **Turn your OpenAPI into a first-class Svelte 5 DX.**
4
+
5
+ Svelte Reflector is a **developer-experience-first code generator** that converts OpenAPI specs into fully typed, reactive Svelte 5 modules — ready for production, forms included.
6
+
7
+ [![npm version](https://img.shields.io/npm/v/svelte-reflector.svg)](https://www.npmjs.com/package/svelte-reflector)
8
+ [![npm downloads](https://img.shields.io/npm/dm/svelte-reflector.svg)](https://www.npmjs.com/package/svelte-reflector)
9
+ [![npm total downloads](https://img.shields.io/npm/dt/svelte-reflector.svg)](https://www.npmjs.com/package/svelte-reflector)
10
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.9+-blue.svg)](https://www.typescriptlang.org/)
11
+ [![Svelte](https://img.shields.io/badge/Svelte-5+-orange.svg)](https://svelte.dev/)
12
+
13
+ ## Features
14
+
15
+ - **Automatic Type Generation** - Generates TypeScript interfaces and classes from OpenAPI schemas
16
+ - **Svelte 5 Runes Integration** - Uses `$state` and `$derived` for reactive state management
17
+ - **Abstract Modules** - Generated modules are abstract classes, ready to be extended with custom logic
18
+ - **Per-Module Schemas** - Each module gets its own schema file with only the types it needs (tree-shaking friendly)
19
+ - **Form Handling** - Auto-generates form schemas with `BuildedInput<T>` wrappers and validation support
20
+ - **Type-Safe API Calls** - Full TypeScript support for all API operations
21
+ - **Query Parameter Sync** - `QueryBuilder` and `EnumQueryBuilder` keep state synced with URL searchParams
22
+ - **Enum Support** - Auto-generates enum types and array enum query builders
23
+ - **OpenAPI/Swagger Compatible** - Works with any backend that exposes OpenAPI specs
24
+ - **Development Mode** - Smart regeneration based on environment
25
+ - **Validation Ready** - Built-in support for custom field validators
26
+ - **Vite Plugin** - Can be used as a Vite plugin for automatic generation on build
27
+
28
+ ## Installation
29
+
30
+ ```bash
31
+ npm install svelte-reflector
32
+ # or
33
+ yarn add svelte-reflector
34
+ # or
35
+ pnpm add svelte-reflector
36
+ ```
37
+
38
+ > **Note:** `prettier` >= 3.0.0 is a required peer dependency. Make sure it's installed in your project.
39
+
40
+ ## Quick Start
41
+
42
+ ### 1. Configure Environment Variables
43
+
44
+ Create a `.env` file in your project root:
45
+
46
+ ```env
47
+ # Required - Your backend URL
48
+ BACKEND_URL=https://api.example.com/
49
+ # or
50
+ PUBLIC_BACKEND=https://api.example.com/
51
+
52
+ # Optional - Environment (defaults to PROD)
53
+ ENVIRONMENT=DEV
54
+ # or
55
+ VITE_ENVIRONMENT=DEV
56
+ ```
57
+
58
+ ### 2. Create Reflector Config (Optional)
59
+
60
+ Create a `src/reflector.config.ts` to define custom validators:
61
+
62
+ ```typescript
63
+ export const validators = [
64
+ {
65
+ fields: ["email", "userEmail"],
66
+ validator: "validateEmail",
67
+ },
68
+ {
69
+ fields: ["phone", "mobile"],
70
+ validator: "validatePhone",
71
+ },
72
+ ];
73
+ ```
74
+
75
+ Validators are resolved from `$lib/sanitizers/validateFormats` — you need to implement and export them in your project.
76
+
77
+ ### 3. Configure API Import Path (Optional)
78
+
79
+ Create a `reflector.json` in your project root to customize the API import path:
80
+
81
+ ```json
82
+ {
83
+ "api": "$lib/api"
84
+ }
85
+ ```
86
+
87
+ Defaults to `$lib/api` if not specified. This is the module that generated modules will import for making HTTP requests.
88
+
89
+ ### 4. Run the Generator
90
+
91
+ ```bash
92
+ # Manual generation
93
+ npx reflect
94
+
95
+ # Or programmatically as a Vite plugin
96
+ import { reflector } from "svelte-reflector";
97
+ await reflector(true); // true = force generation
98
+ ```
99
+
100
+ ### 5. Use Generated Modules
101
+
102
+ Generated modules are **abstract classes**. Extend them to add custom logic or simply to instantiate:
103
+
104
+ ```typescript
105
+ import { UserModule } from "$reflector/controllers/user/user.module.svelte";
106
+ import type { User } from "$reflector/controllers/user/user.schema.svelte";
107
+
108
+ // Extend the abstract module
109
+ class UserService extends UserModule {}
110
+
111
+ const userService = new UserService();
112
+
113
+ // Access reactive state
114
+ console.log(userService.loading); // $state<boolean>
115
+ console.log(userService.list); // $state<User[]>
116
+
117
+ // Call API methods
118
+ await userService.listAll({
119
+ behavior: {
120
+ onSuccess: (response) => console.log(response),
121
+ onError: (error) => console.error(error),
122
+ },
123
+ });
124
+
125
+ // Work with forms
126
+ const userForm = userService.forms.createUser;
127
+ userForm.name.value = "John Doe";
128
+ userForm.email.value = "john@example.com";
129
+
130
+ // Submit form
131
+ await userService.createUser();
132
+ ```
133
+
134
+ ## Generated Structure
135
+
136
+ ```
137
+ src/reflector/
138
+ ├── controllers/
139
+ │ └── user/
140
+ │ ├── user.module.svelte.ts # Abstract API module with methods
141
+ │ └── user.schema.svelte.ts # Schemas & types used by this module
142
+ ├── reflector.svelte.ts # Core utilities (build, isFormValid, QueryBuilder, etc.)
143
+ ├── fields.ts # Field name constants
144
+ ├── enums.ts # Enum type definitions
145
+ ├── mocked-params.svelte.ts # Mocked path parameters ($state)
146
+ └── backup.json # Cached OpenAPI spec
147
+ ```
148
+
149
+ Each module gets its own schema file (`*.schema.svelte.ts`) containing only the schemas it uses, with transitive dependencies automatically resolved.
150
+
151
+ ## Generated Module API
152
+
153
+ Each generated module is an **abstract class** that provides:
154
+
155
+ ### State Properties
156
+
157
+ | Property | Type | Description |
158
+ |----------|------|-------------|
159
+ | `loading` | `$state<boolean>` | Request loading state |
160
+ | `list` | `$state<T[]>` | List results (for list endpoints) |
161
+ | `forms` | `$state<Record<string, T>>` | Form instances |
162
+ | `querys` | `Querys` | Query parameter state (QueryBuilder instances) |
163
+ | `headers` | `Headers` | Header state |
164
+ | `paths` | `Paths` | Path parameter state |
165
+
166
+ ### Methods
167
+
168
+ ```typescript
169
+ // List all items (GET with page parameter)
170
+ async listAll(params?: { behavior?: Behavior }): Promise<T[]>
171
+
172
+ // Get single entity (GET without page parameter)
173
+ async get(params?: { behavior?: Behavior }): Promise<T>
174
+
175
+ // Create/Update (POST/PUT/PATCH)
176
+ async create(params?: { behavior?: Behavior }): Promise<T>
177
+ async update(params?: { behavior?: Behavior }): Promise<T>
178
+
179
+ // Delete (DELETE)
180
+ async delete(params?: { behavior?: Behavior }): Promise<void>
181
+
182
+ // Reset all state (protected)
183
+ protected reset(): void
184
+
185
+ // Clear forms (protected)
186
+ protected clearForms(): void
187
+ ```
188
+
189
+ > `reset()` and `clearForms()` are `protected` — override them in your subclass if you need custom reset behavior.
190
+
191
+ ### QueryBuilder
192
+
193
+ Query parameters are wrapped in `QueryBuilder` / `EnumQueryBuilder` instances
194
+ that read directly from `page.url.searchParams`. There is no local cached
195
+ state — every read goes through the URL, so multiple instances with the same
196
+ key are always coherent.
197
+
198
+ ```typescript
199
+ // Single value query parameter
200
+ const querys = module.querys;
201
+
202
+ querys.status.value; // string | null — read-only getter, always
203
+ // reflects the current URL
204
+ querys.status.update("active"); // pushes ?status=active via goto()
205
+
206
+ // Array enum query parameter
207
+ const enumQuery = module.querys.roles; // EnumQueryBuilder<RoleType>
208
+ enumQuery.selected = "admin";
209
+ enumQuery.add(); // appends to URL searchParams
210
+ enumQuery.remove(0); // removes from URL searchParams
211
+ enumQuery.values; // $derived — always in sync with URL
212
+ ```
213
+
214
+ #### Defaults
215
+
216
+ `default` declared in the OpenAPI schema is propagated to the builder
217
+ constructor automatically:
218
+
219
+ ```yaml
220
+ parameters:
221
+ - name: limit
222
+ in: query
223
+ schema: { type: integer, default: 10 }
224
+ - name: tags
225
+ in: query
226
+ schema:
227
+ type: array
228
+ items: { type: string, enum: [hot, new, sale] }
229
+ default: [hot]
230
+ ```
231
+
232
+ generates:
233
+
234
+ ```typescript
235
+ class Querys {
236
+ readonly limit = new QueryBuilder({ key: 'limit', defaultValue: 10 });
237
+ readonly tags = new EnumQueryBuilder<'hot' | 'new' | 'sale'>({
238
+ key: 'tags',
239
+ defaultValues: ['hot'],
240
+ });
241
+ }
242
+ ```
243
+
244
+ When the URL has the param, the URL value wins. When the URL has no param,
245
+ the default is returned. The URL stays clean until the user interacts.
246
+
247
+ #### Migrating from 1.x
248
+
249
+ `value` is no longer a setter. Two replacements:
250
+
251
+ ```typescript
252
+ // 1.x
253
+ querys.page.value = "1";
254
+ querys.page.value ??= "1"; // seed default
255
+
256
+ // 2.x declarative default (preferred, set at codegen via OpenAPI)
257
+ new QueryBuilder({ key: "page", defaultValue: "1" });
258
+
259
+ // 2.x — imperative update (push to URL)
260
+ querys.page.update("1");
261
+ ```
262
+
263
+ The auto-injected `setQueryGroup([...])` constructor on the generated
264
+ `Querys` class was removed — defaults now live on the builder. Import
265
+ `setQueryGroup` manually from `$reflector/reflector.svelte` if you still
266
+ need batch URL writes.
267
+
268
+ #### Ephemeral pagination (sidebar / widget)
269
+
270
+ Sometimes a list lives outside the canonical route — a global sidebar, a
271
+ widget, a paginated combobox in a modal — and should NOT mutate the
272
+ current URL. For those cases, every generated `call()` with query params
273
+ accepts an optional `queryOverride`:
274
+
275
+ ```typescript
276
+ const sidebar = new UserService();
277
+
278
+ // Current URL stays put. Request goes out with ?page=2&limit=10.
279
+ await sidebar.listAll({
280
+ queryOverride: { page: "2", limit: "10" },
281
+ });
282
+ ```
283
+
284
+ When `queryOverride` is passed, the method skips `this.querys.bundle()`
285
+ entirely and uses the override as `queryData`. Without it, the method
286
+ reads from `QueryBuilder.value` (the URL) as usual — so you can mix and
287
+ match on a per-call basis. Omit a key to drop it from the request; pass
288
+ `null` to send a literal `null`.
289
+
290
+ ## Configuration
291
+
292
+ ### Environment Variables
293
+
294
+ | Variable | Required | Description |
295
+ |----------|----------|-------------|
296
+ | `BACKEND_URL` | Yes* | Backend API URL |
297
+ | `PUBLIC_BACKEND` | Yes* | Alternative to BACKEND_URL |
298
+ | `ENVIRONMENT` | No | DEV/PROD (defaults to PROD) |
299
+ | `VITE_ENVIRONMENT` | No | Vite-specific env var |
300
+ | `NODE_ENV` | No | Node environment |
301
+
302
+ \* At least one of `BACKEND_URL` or `PUBLIC_BACKEND` is required.
303
+
304
+ ### Behavior Pattern
305
+
306
+ All API methods accept a `Behavior` object for callbacks:
307
+
308
+ ```typescript
309
+ class Behavior<TSuccess, TError> {
310
+ onSuccess?: (value: TSuccess) => Promise<void> | void;
311
+ onError?: (error: TError) => Promise<void> | void;
312
+ }
313
+
314
+ // Usage
315
+ await userService.createUser({
316
+ behavior: {
317
+ onSuccess: (user) => console.log("Created:", user),
318
+ onError: (err) => console.error("Failed:", err),
319
+ },
320
+ });
321
+ ```
322
+
323
+ ### Form Validation
324
+
325
+ Forms use `BuildedInput` class with validation:
326
+
327
+ ```typescript
328
+ class BuildedInput<T> {
329
+ value: T; // Current value ($state)
330
+ display: T; // Display value ($state)
331
+ required: boolean; // Is field required
332
+ placeholder: T; // Placeholder/example value
333
+ readonly kind: 'builded';
334
+ validator?: (v: T) => string | null; // Validation function
335
+ validate(): string | null; // Run validation
336
+ }
337
+
338
+ // Check if all form fields are valid
339
+ import { isFormValid } from "$reflector/reflector.svelte";
340
+
341
+ if (isFormValid(userService.forms.createUser)) {
342
+ await userService.createUser();
343
+ }
344
+ ```
345
+
346
+ ## TypeScript Configuration
347
+
348
+ Add path aliases to your `tsconfig.json`:
349
+
350
+ ```json
351
+ {
352
+ "compilerOptions": {
353
+ "paths": {
354
+ "$reflector/*": ["./src/reflector/*"],
355
+ "$lib/*": ["./src/lib/*"]
356
+ }
357
+ }
358
+ }
359
+ ```
360
+
361
+ For Vite projects, also update `vite.config.ts`:
362
+
363
+ ```typescript
364
+ export default defineConfig({
365
+ resolve: {
366
+ alias: {
367
+ $reflector: path.resolve("./src/reflector"),
368
+ $lib: path.resolve("./src/lib"),
369
+ },
370
+ },
371
+ });
372
+ ```
373
+
374
+ ## Workflow
375
+
376
+ ### Development Mode
377
+
378
+ In `ENVIRONMENT=DEV`:
379
+ - Schemas are **NOT** auto-regenerated on build
380
+ - Use `npx reflect` to manually regenerate
381
+ - Faster builds, manual control
382
+
383
+ ### Production Mode
384
+
385
+ In `ENVIRONMENT=PROD`:
386
+ - Schemas are auto-regenerated on each build
387
+ - Fresh types from latest OpenAPI spec
388
+ - Fallback to `backup.json` if backend is unavailable
389
+
390
+ ## Advanced Usage
391
+
392
+ ### Extending Abstract Modules
393
+
394
+ Since modules are abstract, you can add custom logic:
395
+
396
+ ```typescript
397
+ import { UserModule } from "$reflector/controllers/user/user.module.svelte";
398
+
399
+ class UserService extends UserModule {
400
+ // Add custom computed state
401
+ get activeUsers() {
402
+ return this.list.filter(u => u.active);
403
+ }
404
+
405
+ // Override protected methods for custom behavior
406
+ protected override clearForms() {
407
+ super.clearForms();
408
+ // custom cleanup logic
409
+ }
410
+
411
+ // Add custom methods
412
+ async fetchAndFilter(status: string) {
413
+ this.querys.status.update(status);
414
+ await this.listAll();
415
+ }
416
+ }
417
+ ```
418
+
419
+ ### Manual Schema Access
420
+
421
+ ```typescript
422
+ import { User } from "$reflector/controllers/user/user.schema.svelte";
423
+
424
+ // Create instance
425
+ const user = new User({ name: "John", email: "john@example.com" });
426
+
427
+ // Get data bundle
428
+ const data = user.bundle(); // { name: "John", email: "john@example.com" }
429
+ ```
430
+
431
+ ### Batch Query Updates
432
+
433
+ ```typescript
434
+ import { setQueryGroup } from "$reflector/reflector.svelte";
435
+
436
+ // Update multiple query params at once
437
+ setQueryGroup([
438
+ { key: "page", value: 1 },
439
+ { key: "status", value: "active" },
440
+ { key: "roles", value: ["admin", "editor"] }, // Array params supported
441
+ ]);
442
+ ```
443
+
444
+ ## Troubleshooting
445
+
446
+ ### "BACKEND_URL vazio" Error
447
+
448
+ Ensure you have set `BACKEND_URL` or `PUBLIC_BACKEND` in your `.env` file.
449
+
450
+ ### Schemas Not Updating
451
+
452
+ In DEV mode, run `npx reflect` manually. Check that your backend's OpenAPI spec is accessible at `{BACKEND_URL}openapi.json`.
453
+
454
+ ### Type Errors After Generation
455
+
456
+ 1. Restart your TypeScript language server
457
+ 2. Check path aliases in `tsconfig.json`
458
+ 3. Ensure `$reflector/*` alias is configured
459
+
460
+ ## License
461
+
462
+ MIT License - see [LICENSE](LICENSE) for details.
463
+
464
+ ## Contributing
465
+
466
+ Contributions are welcome! Please feel free to submit a Pull Request.
467
+
468
+ ## Links
469
+
470
+ - [npm](https://www.npmjs.com/package/svelte-reflector)
471
+ - [GitHub](https://github.com/aleleppy/reflector)
472
+ - [Svelte](https://svelte.dev/)
473
+ - [OpenAPI Specification](https://swagger.io/specification/)
474
+
475
+ ---
476
+
477
+ Built with by the Pinaculo Digital team.
@@ -44,7 +44,7 @@ export class CallMethodGenerator {
44
44
  const { querys, paths, cookies } = method.analyzers.props;
45
45
  const lines = [];
46
46
  if (querys.length > 0) {
47
- lines.push(`const { ${this.joinNames(querys)} } = this.querys.bundle()`);
47
+ lines.push(`const { ${this.joinNames(querys)} } = params?.queryOverride ?? this.querys.bundle()`);
48
48
  }
49
49
  if (paths.length > 0) {
50
50
  lines.push(`const { ${this.joinNames(paths)} } = params?.paths ?? this.paths`);
@@ -6,4 +6,6 @@ export declare class ModuleCallStrategy implements CallStrategy {
6
6
  formStateAccess(method: CallMethodInput): string;
7
7
  private buildParamsType;
8
8
  private buildPathsBlock;
9
+ private buildQueryOverrideBlock;
10
+ private queryOverrideType;
9
11
  }
@@ -17,27 +17,43 @@ export class ModuleCallStrategy {
17
17
  buildParamsType(method) {
18
18
  const behaviorType = `Behavior<${method.responseTypeInterface}, ApiErrorResponse>`;
19
19
  const pathsBlock = this.buildPathsBlock(method);
20
- if (pathsBlock) {
21
- return `{
22
- behavior?: ${behaviorType};${pathsBlock}
23
- }`;
24
- }
20
+ const queryBlock = this.buildQueryOverrideBlock(method);
21
+ const blocks = [`behavior?: ${behaviorType};`, pathsBlock, queryBlock]
22
+ .filter((b) => !!b)
23
+ .join("\n");
25
24
  return `{
26
- behavior?: ${behaviorType};
25
+ ${blocks}
27
26
  }`;
28
27
  }
29
28
  buildPathsBlock(method) {
30
29
  const paths = method.analyzers.props.paths;
31
30
  if (paths.length === 0)
32
31
  return undefined;
33
- return `
34
- paths?: {
32
+ return `paths?: {
35
33
  ${paths
36
34
  .map((p) => {
37
35
  const type = p.rawType ?? p.type;
38
36
  return `${p.name}: ${type}`;
39
37
  })
40
38
  .join("\n")}
41
- }`;
39
+ };`;
40
+ }
41
+ buildQueryOverrideBlock(method) {
42
+ const querys = method.analyzers.props.querys;
43
+ if (querys.length === 0)
44
+ return undefined;
45
+ const fields = querys
46
+ .map((q) => `${q.name}?: ${this.queryOverrideType(q)}`)
47
+ .join("\n");
48
+ return `queryOverride?: {
49
+ ${fields}
50
+ };`;
51
+ }
52
+ queryOverrideType(q) {
53
+ if ("isEnum" in q && q.isEnum)
54
+ return `${q.type}[]`;
55
+ if (!("rawType" in q) && !("enumName" in q))
56
+ return "string[]";
57
+ return "string | null";
42
58
  }
43
59
  }
@@ -26,8 +26,6 @@ export class ModuleClassBuilder {
26
26
  `;
27
27
  }
28
28
  else if (name === "Querys") {
29
- this.imports.addSetQueryGroupImport();
30
- const queryGroupValues = [];
31
29
  props.forEach((prop) => {
32
30
  if ("rawType" in prop) {
33
31
  attributes.push(prop.queryBuild());
@@ -35,13 +33,9 @@ export class ModuleClassBuilder {
35
33
  this.imports.addReflectorImport("EnumQueryBuilder");
36
34
  this.imports.addEnumImport(String(prop.type));
37
35
  bundle.push(`${prop.name}: this.${prop.name}?.values`);
38
- // Array de enum usa valor padrão []
39
- queryGroupValues.push(`{ key: '${prop.name}', value: ${prop.queryDefaultValue()} }`);
40
36
  }
41
37
  else {
42
38
  bundle.push(prop.bundleBuild());
43
- // PrimitiveProp usa seu valor padrão
44
- queryGroupValues.push(`{ key: '${prop.name}', value: ${prop.queryDefaultValue()} }`);
45
39
  }
46
40
  }
47
41
  else if ("enumName" in prop) {
@@ -49,7 +43,6 @@ export class ModuleClassBuilder {
49
43
  this.imports.addEnumImport(prop.enumName);
50
44
  attributes.push(prop.queryBuild());
51
45
  bundle.push(prop.bundleBuild());
52
- queryGroupValues.push(`{ key: '${prop.name}', value: ${prop.queryDefaultValue()} }`);
53
46
  }
54
47
  else {
55
48
  // ArrayProp (não-enum)
@@ -57,16 +50,8 @@ export class ModuleClassBuilder {
57
50
  this.imports.addEnumImport(prop.type);
58
51
  bundle.push(prop.queryBundleBuild());
59
52
  this.imports.addReflectorImport("EnumQueryBuilder");
60
- queryGroupValues.push(`{ key: '${prop.name}', value: ${prop.queryDefaultValue()} }`);
61
53
  }
62
54
  });
63
- const constructorBuild = `
64
- constructor() {
65
- setQueryGroup([
66
- ${queryGroupValues.join(",\n ")}
67
- ]);
68
- }
69
- `;
70
55
  if (bundle.length > 0) {
71
56
  this.imports.addReflectorImport("bundleStrict");
72
57
  }
@@ -74,8 +59,6 @@ export class ModuleClassBuilder {
74
59
  class ${outputName} {
75
60
  ${attributes.join(";")}
76
61
 
77
- ${constructorBuild}
78
-
79
62
  ${bundle.length > 0 ? `
80
63
  bundle() {
81
64
  return bundleStrict({
@@ -9,7 +9,6 @@ export declare class ModuleImports {
9
9
  addImport(importStr: string): void;
10
10
  addMockedImport(importStr: string): void;
11
11
  addReflectorImport(importStr: string): void;
12
- addSetQueryGroupImport(): void;
13
12
  addEnumImport(enumName: string): void;
14
13
  addPageStateImport(): void;
15
14
  getImportsArray(): string[];
@@ -25,9 +25,6 @@ export class ModuleImports {
25
25
  addReflectorImport(importStr) {
26
26
  this.reflectorImports.add(importStr);
27
27
  }
28
- addSetQueryGroupImport() {
29
- this.reflectorImports.add("setQueryGroup");
30
- }
31
28
  addEnumImport(enumName) {
32
29
  this.enumImports.add(enumName);
33
30
  }
@@ -55,16 +52,12 @@ export class ModuleImports {
55
52
  typeOnlyImports = new Set(["ApiCallParams"]);
56
53
  buildReflectorImportsLine() {
57
54
  const imports = this.getReflectorImportsArray();
58
- const regularImports = imports.filter(i => i !== "setQueryGroup" && !this.typeOnlyImports.has(i));
59
- const typeImports = imports.filter(i => this.typeOnlyImports.has(i));
60
- const hasSetQueryGroup = imports.includes("setQueryGroup");
55
+ const regularImports = imports.filter((i) => !this.typeOnlyImports.has(i));
56
+ const typeImports = imports.filter((i) => this.typeOnlyImports.has(i));
61
57
  let result = `import { ${regularImports.join(", ")}, type ApiErrorResponse`;
62
58
  for (const t of typeImports) {
63
59
  result += `, type ${t}`;
64
60
  }
65
- if (hasSetQueryGroup) {
66
- result += `, setQueryGroup`;
67
- }
68
61
  result += ` } from "${this.config.reflectorAlias}/reflector.svelte";`;
69
62
  return result;
70
63
  }
@@ -8,6 +8,7 @@ export declare class ArrayProp {
8
8
  private _isPrimitiveType;
9
9
  private readonly isNullable;
10
10
  private readonly context;
11
+ private readonly defaultValues;
11
12
  get isSchemaRef(): boolean;
12
13
  readonly isEnum: boolean;
13
14
  constructor(params: {
@@ -28,5 +29,4 @@ export declare class ArrayProp {
28
29
  queryBundleBuild(): string;
29
30
  queryBuild(): string;
30
31
  staticBuild(): string;
31
- queryDefaultValue(): string;
32
32
  }
@@ -9,6 +9,7 @@ export class ArrayProp {
9
9
  _isPrimitiveType = false;
10
10
  isNullable;
11
11
  context;
12
+ defaultValues;
12
13
  get isSchemaRef() {
13
14
  return !this._isPrimitiveType && !this.isEnum;
14
15
  }
@@ -22,6 +23,7 @@ export class ArrayProp {
22
23
  this.type = this.getType({ schemaObject, schemaName });
23
24
  this.isRequired = required;
24
25
  this.isParam = !!isParam;
26
+ this.defaultValues = Array.isArray(schemaObject.default) ? schemaObject.default : [];
25
27
  }
26
28
  getType(params) {
27
29
  const { schemaObject, schemaName } = params;
@@ -82,10 +84,15 @@ export class ArrayProp {
82
84
  }
83
85
  queryBuild() {
84
86
  if (this.isEnum) {
85
- return `readonly ${this.name} = $derived(new EnumQueryBuilder<${this.type}>({ key: '${this.name}' }))`;
87
+ if (this.defaultValues.length === 0) {
88
+ return `readonly ${this.name} = new EnumQueryBuilder<${this.type}>({ key: '${this.name}' })`;
89
+ }
90
+ const literal = `[${this.defaultValues
91
+ .map((v) => (typeof v === "string" ? `'${v}'` : String(v)))
92
+ .join(", ")}]`;
93
+ return `readonly ${this.name} = new EnumQueryBuilder<${this.type}>({ key: '${this.name}', defaultValues: ${literal} })`;
86
94
  }
87
- // Para arrays normais (não enum), usamos QueryBuilder padrão
88
- return `readonly ${this.name} = $derived(new QueryBuilder({ key: '${this.name}' }))`;
95
+ return `readonly ${this.name} = new QueryBuilder({ key: '${this.name}' })`;
89
96
  }
90
97
  staticBuild() {
91
98
  const result = this._isPrimitiveType ? "obj" : `new ${this.type}({ data: obj })`;
@@ -96,7 +103,4 @@ export class ArrayProp {
96
103
  }
97
104
  `;
98
105
  }
99
- queryDefaultValue() {
100
- return "[]";
101
- }
102
106
  }
@@ -19,5 +19,4 @@ export declare class EnumProp {
19
19
  interfaceBuild(): string;
20
20
  queryBuild(): string;
21
21
  bundleBuild(): string;
22
- queryDefaultValue(): string;
23
22
  }
@@ -34,12 +34,9 @@ export class EnumProp {
34
34
  return `${this.name}${req}: ${this.enumName}`;
35
35
  }
36
36
  queryBuild() {
37
- return `readonly ${this.name} = $derived(new QueryBuilder({ key: '${this.name}' }))`;
37
+ return `readonly ${this.name} = new QueryBuilder({ key: '${this.name}' })`;
38
38
  }
39
39
  bundleBuild() {
40
40
  return `${this.name}: this.${this.name}?.value`;
41
41
  }
42
- queryDefaultValue() {
43
- return `'${this.example}'`;
44
- }
45
42
  }
@@ -1,7 +1,6 @@
1
1
  import type { SchemaObject } from "../types/open-api-spec.interface.js";
2
2
  import type { ReflectorParamType } from "../types/types.js";
3
3
  type AbstractType = string | boolean | number | undefined;
4
- type Example = string | boolean | number;
5
4
  export declare class PrimitiveProp {
6
5
  name: string;
7
6
  type: AbstractType;
@@ -14,6 +13,7 @@ export declare class PrimitiveProp {
14
13
  private readonly buildedConst;
15
14
  private readonly example;
16
15
  private readonly fallbackExample;
16
+ private readonly defaultValue;
17
17
  private get effectiveType();
18
18
  /** Non-required fields become nullable (| null) instead of optional (?) */
19
19
  private get isEffectivelyNullable();
@@ -37,6 +37,5 @@ export declare class PrimitiveProp {
37
37
  queryBuild(): string;
38
38
  updateQueryBuild(): string;
39
39
  bundleBuild(): string;
40
- queryDefaultValue(): Example;
41
40
  }
42
41
  export {};
@@ -12,6 +12,7 @@ export class PrimitiveProp {
12
12
  buildedConst;
13
13
  example;
14
14
  fallbackExample;
15
+ defaultValue;
15
16
  get effectiveType() {
16
17
  return this.customType ?? this.rawType;
17
18
  }
@@ -28,6 +29,7 @@ export class PrimitiveProp {
28
29
  const { emptyExample, example } = this.getExampleAndFallback({ schemaObject, type, name });
29
30
  this.example = example;
30
31
  this.fallbackExample = emptyExample;
32
+ this.defaultValue = schemaObject.default;
31
33
  const buildedType = customType ?? type;
32
34
  const treated = treatPropertyName(name);
33
35
  this.name = treated.name;
@@ -132,7 +134,13 @@ export class PrimitiveProp {
132
134
  return `readonly ${this.name} = $derived.by(() => '${this.name}' in page.params ? page.params.${this.name} : mockedParams.${this.name}) as string | null;`;
133
135
  }
134
136
  queryBuild() {
135
- return `readonly ${this.name} = $derived(new QueryBuilder({ key: '${this.name}' }))`;
137
+ if (this.defaultValue === undefined || this.defaultValue === null) {
138
+ return `readonly ${this.name} = new QueryBuilder({ key: '${this.name}' })`;
139
+ }
140
+ const literal = typeof this.defaultValue === "string"
141
+ ? `'${this.defaultValue}'`
142
+ : String(this.defaultValue);
143
+ return `readonly ${this.name} = new QueryBuilder({ key: '${this.name}', defaultValue: ${literal} })`;
136
144
  }
137
145
  updateQueryBuild() {
138
146
  return `${this.name}: (event: SvelteEvent) => changeParam({ key: '${this.name}', event })`;
@@ -140,7 +148,4 @@ export class PrimitiveProp {
140
148
  bundleBuild() {
141
149
  return `${this.name}: ${this.thisDot()}${this.name}?.value`;
142
150
  }
143
- queryDefaultValue() {
144
- return this.fallbackExample;
145
- }
146
151
  }
@@ -9,9 +9,10 @@ type ValidatorResult = string | null;
9
9
  type ValidatorFn<T> = (v: T) => ValidatorResult;
10
10
  type BundleResult<T> = T extends { bundle: () => infer R } ? R : T;
11
11
 
12
- export type ApiCallParams<TResponse, TPaths = void> = TPaths extends void
13
- ? { behavior?: Behavior<TResponse, ApiErrorResponse> }
14
- : { behavior?: Behavior<TResponse, ApiErrorResponse>; paths?: TPaths };
12
+ export type ApiCallParams<TResponse, TPaths = void, TQuery = void> = {
13
+ behavior?: Behavior<TResponse, ApiErrorResponse>;
14
+ } & (TPaths extends void ? object : { paths?: TPaths }) &
15
+ (TQuery extends void ? object : { queryOverride?: TQuery });
15
16
 
16
17
  type PartialBuildedInput<T> = {
17
18
  [K in Exclude<keyof T, "bundle">]?: BuildedInput<T[K]>;
@@ -79,13 +80,18 @@ export class BuildedInput<T> {
79
80
 
80
81
  export class EnumQueryBuilder<T> {
81
82
  readonly key: string = "";
82
- values = $derived(page.url.searchParams.getAll(this.key)) as T[];
83
- selected = $state<T | null>(null);
83
+ private readonly defaultValues: T[] = [];
84
84
 
85
- constructor(params: { key: string }) {
86
- const { key } = params;
85
+ values = $derived(
86
+ page.url.searchParams.has(this.key)
87
+ ? (page.url.searchParams.getAll(this.key) as T[])
88
+ : this.defaultValues,
89
+ );
90
+ selected = $state<T | null>(null);
87
91
 
88
- this.key = key;
92
+ constructor(params: { key: string; defaultValues?: T[] }) {
93
+ this.key = params.key;
94
+ this.defaultValues = params.defaultValues ?? [];
89
95
  }
90
96
 
91
97
  add = () => {
@@ -167,22 +173,26 @@ type QueryWithArrayType = {
167
173
  };
168
174
 
169
175
  export class QueryBuilder {
170
- readonly key: string = "";
171
- value = $state<string | null>(null);
176
+ readonly key: string;
172
177
  readonly kind = "query";
178
+ private readonly defaultValue: string | null;
179
+
180
+ constructor(params: { key: string; defaultValue?: string | number | null }) {
181
+ this.key = params.key;
182
+ this.defaultValue =
183
+ params.defaultValue === undefined || params.defaultValue === null
184
+ ? null
185
+ : String(params.defaultValue);
186
+ }
173
187
 
174
- constructor(params: { key: string }) {
175
- const { key } = params;
176
- this.key = key;
177
-
178
- const urlValue = page.url.searchParams.get(key);
179
- this.value = urlValue !== null ? urlValue : null;
188
+ get value(): string | null {
189
+ const fromUrl = page.url.searchParams.get(this.key);
190
+ return fromUrl !== null ? fromUrl : this.defaultValue;
180
191
  }
181
192
 
182
193
  update(event: string | number | null) {
183
194
  if (event === null || event === undefined) return;
184
- this.value = String(event);
185
- return changeParam({ key: this.key, event: this.value });
195
+ return changeParam({ key: this.key, event: String(event) });
186
196
  }
187
197
  }
188
198
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-reflector",
3
- "version": "1.3.11",
3
+ "version": "2.1.0",
4
4
  "description": "Reflects zod types from openAPI schemas",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -21,7 +21,8 @@
21
21
  "build": "tsc && tsc -p tsconfig.runtime.json && node scripts/copy-runtime.mjs",
22
22
  "typecheck:runtime": "tsc -p tsconfig.runtime.json",
23
23
  "test": "vitest run",
24
- "test:watch": "vitest"
24
+ "test:watch": "vitest",
25
+ "patch": "npm run build && npm version patch && npm publish"
25
26
  },
26
27
  "dependencies": {
27
28
  "axios": "^1.12.2",