svelte-reflector 1.3.11 → 2.0.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,455 @@
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
+ ## Configuration
269
+
270
+ ### Environment Variables
271
+
272
+ | Variable | Required | Description |
273
+ |----------|----------|-------------|
274
+ | `BACKEND_URL` | Yes* | Backend API URL |
275
+ | `PUBLIC_BACKEND` | Yes* | Alternative to BACKEND_URL |
276
+ | `ENVIRONMENT` | No | DEV/PROD (defaults to PROD) |
277
+ | `VITE_ENVIRONMENT` | No | Vite-specific env var |
278
+ | `NODE_ENV` | No | Node environment |
279
+
280
+ \* At least one of `BACKEND_URL` or `PUBLIC_BACKEND` is required.
281
+
282
+ ### Behavior Pattern
283
+
284
+ All API methods accept a `Behavior` object for callbacks:
285
+
286
+ ```typescript
287
+ class Behavior<TSuccess, TError> {
288
+ onSuccess?: (value: TSuccess) => Promise<void> | void;
289
+ onError?: (error: TError) => Promise<void> | void;
290
+ }
291
+
292
+ // Usage
293
+ await userService.createUser({
294
+ behavior: {
295
+ onSuccess: (user) => console.log("Created:", user),
296
+ onError: (err) => console.error("Failed:", err),
297
+ },
298
+ });
299
+ ```
300
+
301
+ ### Form Validation
302
+
303
+ Forms use `BuildedInput` class with validation:
304
+
305
+ ```typescript
306
+ class BuildedInput<T> {
307
+ value: T; // Current value ($state)
308
+ display: T; // Display value ($state)
309
+ required: boolean; // Is field required
310
+ placeholder: T; // Placeholder/example value
311
+ readonly kind: 'builded';
312
+ validator?: (v: T) => string | null; // Validation function
313
+ validate(): string | null; // Run validation
314
+ }
315
+
316
+ // Check if all form fields are valid
317
+ import { isFormValid } from "$reflector/reflector.svelte";
318
+
319
+ if (isFormValid(userService.forms.createUser)) {
320
+ await userService.createUser();
321
+ }
322
+ ```
323
+
324
+ ## TypeScript Configuration
325
+
326
+ Add path aliases to your `tsconfig.json`:
327
+
328
+ ```json
329
+ {
330
+ "compilerOptions": {
331
+ "paths": {
332
+ "$reflector/*": ["./src/reflector/*"],
333
+ "$lib/*": ["./src/lib/*"]
334
+ }
335
+ }
336
+ }
337
+ ```
338
+
339
+ For Vite projects, also update `vite.config.ts`:
340
+
341
+ ```typescript
342
+ export default defineConfig({
343
+ resolve: {
344
+ alias: {
345
+ $reflector: path.resolve("./src/reflector"),
346
+ $lib: path.resolve("./src/lib"),
347
+ },
348
+ },
349
+ });
350
+ ```
351
+
352
+ ## Workflow
353
+
354
+ ### Development Mode
355
+
356
+ In `ENVIRONMENT=DEV`:
357
+ - Schemas are **NOT** auto-regenerated on build
358
+ - Use `npx reflect` to manually regenerate
359
+ - Faster builds, manual control
360
+
361
+ ### Production Mode
362
+
363
+ In `ENVIRONMENT=PROD`:
364
+ - Schemas are auto-regenerated on each build
365
+ - Fresh types from latest OpenAPI spec
366
+ - Fallback to `backup.json` if backend is unavailable
367
+
368
+ ## Advanced Usage
369
+
370
+ ### Extending Abstract Modules
371
+
372
+ Since modules are abstract, you can add custom logic:
373
+
374
+ ```typescript
375
+ import { UserModule } from "$reflector/controllers/user/user.module.svelte";
376
+
377
+ class UserService extends UserModule {
378
+ // Add custom computed state
379
+ get activeUsers() {
380
+ return this.list.filter(u => u.active);
381
+ }
382
+
383
+ // Override protected methods for custom behavior
384
+ protected override clearForms() {
385
+ super.clearForms();
386
+ // custom cleanup logic
387
+ }
388
+
389
+ // Add custom methods
390
+ async fetchAndFilter(status: string) {
391
+ this.querys.status.update(status);
392
+ await this.listAll();
393
+ }
394
+ }
395
+ ```
396
+
397
+ ### Manual Schema Access
398
+
399
+ ```typescript
400
+ import { User } from "$reflector/controllers/user/user.schema.svelte";
401
+
402
+ // Create instance
403
+ const user = new User({ name: "John", email: "john@example.com" });
404
+
405
+ // Get data bundle
406
+ const data = user.bundle(); // { name: "John", email: "john@example.com" }
407
+ ```
408
+
409
+ ### Batch Query Updates
410
+
411
+ ```typescript
412
+ import { setQueryGroup } from "$reflector/reflector.svelte";
413
+
414
+ // Update multiple query params at once
415
+ setQueryGroup([
416
+ { key: "page", value: 1 },
417
+ { key: "status", value: "active" },
418
+ { key: "roles", value: ["admin", "editor"] }, // Array params supported
419
+ ]);
420
+ ```
421
+
422
+ ## Troubleshooting
423
+
424
+ ### "BACKEND_URL vazio" Error
425
+
426
+ Ensure you have set `BACKEND_URL` or `PUBLIC_BACKEND` in your `.env` file.
427
+
428
+ ### Schemas Not Updating
429
+
430
+ In DEV mode, run `npx reflect` manually. Check that your backend's OpenAPI spec is accessible at `{BACKEND_URL}openapi.json`.
431
+
432
+ ### Type Errors After Generation
433
+
434
+ 1. Restart your TypeScript language server
435
+ 2. Check path aliases in `tsconfig.json`
436
+ 3. Ensure `$reflector/*` alias is configured
437
+
438
+ ## License
439
+
440
+ MIT License - see [LICENSE](LICENSE) for details.
441
+
442
+ ## Contributing
443
+
444
+ Contributions are welcome! Please feel free to submit a Pull Request.
445
+
446
+ ## Links
447
+
448
+ - [npm](https://www.npmjs.com/package/svelte-reflector)
449
+ - [GitHub](https://github.com/aleleppy/reflector)
450
+ - [Svelte](https://svelte.dev/)
451
+ - [OpenAPI Specification](https://swagger.io/specification/)
452
+
453
+ ---
454
+
455
+ Built with by the Pinaculo Digital team.
@@ -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
  }
@@ -79,13 +79,18 @@ export class BuildedInput<T> {
79
79
 
80
80
  export class EnumQueryBuilder<T> {
81
81
  readonly key: string = "";
82
- values = $derived(page.url.searchParams.getAll(this.key)) as T[];
83
- selected = $state<T | null>(null);
82
+ private readonly defaultValues: T[] = [];
84
83
 
85
- constructor(params: { key: string }) {
86
- const { key } = params;
84
+ values = $derived(
85
+ page.url.searchParams.has(this.key)
86
+ ? (page.url.searchParams.getAll(this.key) as T[])
87
+ : this.defaultValues,
88
+ );
89
+ selected = $state<T | null>(null);
87
90
 
88
- this.key = key;
91
+ constructor(params: { key: string; defaultValues?: T[] }) {
92
+ this.key = params.key;
93
+ this.defaultValues = params.defaultValues ?? [];
89
94
  }
90
95
 
91
96
  add = () => {
@@ -167,22 +172,26 @@ type QueryWithArrayType = {
167
172
  };
168
173
 
169
174
  export class QueryBuilder {
170
- readonly key: string = "";
171
- value = $state<string | null>(null);
175
+ readonly key: string;
172
176
  readonly kind = "query";
177
+ private readonly defaultValue: string | null;
178
+
179
+ constructor(params: { key: string; defaultValue?: string | number | null }) {
180
+ this.key = params.key;
181
+ this.defaultValue =
182
+ params.defaultValue === undefined || params.defaultValue === null
183
+ ? null
184
+ : String(params.defaultValue);
185
+ }
173
186
 
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;
187
+ get value(): string | null {
188
+ const fromUrl = page.url.searchParams.get(this.key);
189
+ return fromUrl !== null ? fromUrl : this.defaultValue;
180
190
  }
181
191
 
182
192
  update(event: string | number | null) {
183
193
  if (event === null || event === undefined) return;
184
- this.value = String(event);
185
- return changeParam({ key: this.key, event: this.value });
194
+ return changeParam({ key: this.key, event: String(event) });
186
195
  }
187
196
  }
188
197
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-reflector",
3
- "version": "1.3.11",
3
+ "version": "2.0.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",