@glitchproof/form-field-generator 1.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.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +491 -0
  3. package/dist/__test__/convert.test.d.ts +2 -0
  4. package/dist/__test__/convert.test.d.ts.map +1 -0
  5. package/dist/__test__/convert.test.js +429 -0
  6. package/dist/__test__/convert.test.js.map +1 -0
  7. package/dist/__test__/create-index-formatter.test.d.ts +2 -0
  8. package/dist/__test__/create-index-formatter.test.d.ts.map +1 -0
  9. package/dist/__test__/create-index-formatter.test.js +17 -0
  10. package/dist/__test__/create-index-formatter.test.js.map +1 -0
  11. package/dist/__test__/is-list.test.d.ts +2 -0
  12. package/dist/__test__/is-list.test.d.ts.map +1 -0
  13. package/dist/__test__/is-list.test.js +13 -0
  14. package/dist/__test__/is-list.test.js.map +1 -0
  15. package/dist/__test__/to-snake-case.test.d.ts +2 -0
  16. package/dist/__test__/to-snake-case.test.d.ts.map +1 -0
  17. package/dist/__test__/to-snake-case.test.js +14 -0
  18. package/dist/__test__/to-snake-case.test.js.map +1 -0
  19. package/dist/core/convert.d.ts +3 -0
  20. package/dist/core/convert.d.ts.map +1 -0
  21. package/dist/core/convert.js +55 -0
  22. package/dist/core/convert.js.map +1 -0
  23. package/dist/index.d.ts +4 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.js +4 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/type.d.ts +61 -0
  28. package/dist/type.d.ts.map +1 -0
  29. package/dist/type.js +2 -0
  30. package/dist/type.js.map +1 -0
  31. package/dist/utils/create-index-formatter.d.ts +2 -0
  32. package/dist/utils/create-index-formatter.d.ts.map +1 -0
  33. package/dist/utils/create-index-formatter.js +15 -0
  34. package/dist/utils/create-index-formatter.js.map +1 -0
  35. package/dist/utils/is-list.d.ts +2 -0
  36. package/dist/utils/is-list.d.ts.map +1 -0
  37. package/dist/utils/is-list.js +2 -0
  38. package/dist/utils/is-list.js.map +1 -0
  39. package/dist/utils/to-snake-case.d.ts +2 -0
  40. package/dist/utils/to-snake-case.d.ts.map +1 -0
  41. package/dist/utils/to-snake-case.js +10 -0
  42. package/dist/utils/to-snake-case.js.map +1 -0
  43. package/package.json +46 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 GlitchProof
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,491 @@
1
+ # TypeScript Fields Generator
2
+
3
+ > Type-safe field path generation for nested objects and arrays
4
+
5
+ Build compile-time validated field accessors from your data schemas. Eliminate typos, refactoring errors, and manual path string management.
6
+
7
+ ```typescript
8
+ const fields = generateFields({
9
+ user: {
10
+ profile: { firstName: 'firstName', email: 'email' },
11
+ addresses: [{ street: 'street', city: 'city' }],
12
+ },
13
+ });
14
+
15
+ fields.$USER.$PROFILE.FIRST_NAME_FIELD; // 'user.profile.firstName'
16
+ fields.$USER.$ADDRESSES.STREET_FIELD(0); // 'user.addresses.0.street'
17
+ ```
18
+
19
+ ## Why?
20
+
21
+ **The Problem:**
22
+
23
+ ```typescript
24
+ // Brittle string paths everywhere
25
+ <input {...register('user.profile.firstName')} />
26
+ db.select('user.addresses.0.city')
27
+ errors['user.profile.email'] // typo? good luck finding it
28
+ ```
29
+
30
+ **The Solution:**
31
+
32
+ ```typescript
33
+ // Type-safe, refactor-friendly, autocomplete-enabled
34
+ <input {...register(fields.$USER.$PROFILE.FIRST_NAME_FIELD)} />
35
+ db.select(fields.$USER.$ADDRESSES.CITY_FIELD(0))
36
+ errors[fields.$USER.$PROFILE.EMAIL_FIELD] // TypeScript catches typos
37
+ ```
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ npm install @glitchproof/form-field-generator
43
+ ```
44
+
45
+ ## Core Concepts
46
+
47
+ ### Simple Fields
48
+
49
+ Input values become uppercase constants and field paths:
50
+
51
+ ```typescript
52
+ const fields = generateFields({
53
+ email: 'email',
54
+ firstName: 'firstName',
55
+ });
56
+
57
+ fields.EMAIL; // 'email' - the value
58
+ fields.EMAIL_FIELD; // 'email' - the path
59
+ fields.FIRST_NAME_FIELD; // 'firstName' - camelCase → SCREAMING_SNAKE_CASE
60
+ ```
61
+
62
+ ### Nested Objects
63
+
64
+ Nested structures get `$` prefixed accessors:
65
+
66
+ ```typescript
67
+ const fields = generateFields({
68
+ user: {
69
+ name: 'name',
70
+ email: 'email',
71
+ },
72
+ });
73
+
74
+ fields.$USER.NAME_FIELD; // 'user.name'
75
+ fields.$USER.EMAIL_FIELD; // 'user.email'
76
+ fields.$USER.KEY; // 'user' - original key
77
+ ```
78
+
79
+ ### Arrays
80
+
81
+ Array fields become functions that accept indices:
82
+
83
+ ```typescript
84
+ const fields = generateFields({
85
+ users: [{ name: 'name', email: 'email' }],
86
+ });
87
+
88
+ fields.$USERS.NAME_FIELD(0); // 'users.0.name'
89
+ fields.$USERS.EMAIL_FIELD(5); // 'users.5.email'
90
+ fields.USERS_FIELD(); // '.users' - the array itself
91
+ ```
92
+
93
+ ### Nested Arrays
94
+
95
+ Multiple indices for deeply nested arrays:
96
+
97
+ ```typescript
98
+ const fields = generateFields({
99
+ orders: [
100
+ {
101
+ items: [{ productId: 'productId', qty: 'qty' }],
102
+ },
103
+ ],
104
+ });
105
+
106
+ fields.$ORDERS.$ITEMS.PRODUCT_ID_FIELD(0, 2); // 'orders.0.items.2.productId'
107
+ // ^ ^
108
+ // order item
109
+ ```
110
+
111
+ ## Real-World Usage
112
+
113
+ ### React Hook Form
114
+
115
+ Stop hardcoding form field paths:
116
+
117
+ ```typescript
118
+ import { useForm } from 'react-hook-form';
119
+
120
+ const formFields = generateFields({
121
+ email: 'email',
122
+ password: 'password',
123
+ profile: {
124
+ firstName: 'firstName',
125
+ lastName: 'lastName'
126
+ }
127
+ });
128
+
129
+ function RegistrationForm() {
130
+ const { register, formState: { errors } } = useForm();
131
+
132
+ return (
133
+ <form>
134
+ <input {...register(formFields.EMAIL_FIELD)} />
135
+ {errors[formFields.EMAIL] && <span>Email required</span>}
136
+
137
+ <input {...register(formFields.$PROFILE.FIRST_NAME_FIELD)} />
138
+ <input {...register(formFields.$PROFILE.LAST_NAME_FIELD)} />
139
+ </form>
140
+ );
141
+ }
142
+ ```
143
+
144
+ **Benefits:**
145
+
146
+ - Rename `firstName` → `givenName`? Change once in schema, works everywhere
147
+ - TypeScript autocomplete guides you
148
+ - Impossible to typo field names
149
+
150
+ ### Database Queries
151
+
152
+ Build type-safe query builders:
153
+
154
+ ```typescript
155
+ const schema = generateFields({
156
+ id: 'id',
157
+ title: 'title',
158
+ author: {
159
+ name: 'name',
160
+ email: 'email',
161
+ },
162
+ tags: [{ name: 'name' }],
163
+ });
164
+
165
+ // Prisma-style
166
+ db.posts.findMany({
167
+ select: {
168
+ [schema.ID]: true,
169
+ [schema.TITLE]: true,
170
+ [schema.$AUTHOR.NAME]: true,
171
+ },
172
+ where: {
173
+ [schema.$AUTHOR.EMAIL_FIELD]: 'user@example.com',
174
+ },
175
+ });
176
+
177
+ // SQL builder
178
+ query()
179
+ .select(schema.TITLE_FIELD)
180
+ .where(schema.$AUTHOR.NAME_FIELD, '=', 'John')
181
+ .orderBy(schema.ID_FIELD);
182
+ ```
183
+
184
+ ### Validation Schemas
185
+
186
+ Stop duplicating field paths:
187
+
188
+ ```typescript
189
+ import { z } from 'zod';
190
+
191
+ const userFields = generateFields({
192
+ email: 'email',
193
+ password: 'password',
194
+ profile: {
195
+ age: 'age',
196
+ },
197
+ });
198
+
199
+ // Define validation once
200
+ const schema = z.object({
201
+ [userFields.EMAIL]: z.string().email(),
202
+ [userFields.PASSWORD]: z.string().min(8),
203
+ [userFields.$PROFILE.AGE]: z.number().min(18),
204
+ });
205
+
206
+ // Use in forms, API validation, etc.
207
+ schema.parse(formData);
208
+ ```
209
+
210
+ ### State Management
211
+
212
+ Type-safe selectors and reducers:
213
+
214
+ ```typescript
215
+ const stateFields = generateFields({
216
+ user: {
217
+ profile: { name: 'name' },
218
+ preferences: { theme: 'theme' },
219
+ },
220
+ session: {
221
+ token: 'token',
222
+ expiresAt: 'expiresAt',
223
+ },
224
+ });
225
+
226
+ // Redux selectors
227
+ const selectUserName = (state) =>
228
+ state[stateFields.$USER.$PROFILE.KEY][stateFields.$USER.$PROFILE.NAME];
229
+
230
+ // Zustand
231
+ const useStore = create((set) => ({
232
+ [stateFields.$USER.KEY]: {},
233
+ [stateFields.$SESSION.KEY]: {},
234
+ }));
235
+ ```
236
+
237
+ ### API Field Selection
238
+
239
+ Control exactly what data you fetch:
240
+
241
+ ```typescript
242
+ const apiFields = generateFields({
243
+ user: {
244
+ id: 'id',
245
+ email: 'email',
246
+ posts: [
247
+ {
248
+ title: 'title',
249
+ comments: [{ text: 'text' }],
250
+ },
251
+ ],
252
+ },
253
+ });
254
+
255
+ // GraphQL
256
+ const query = gql`
257
+ query {
258
+ user {
259
+ ${apiFields.$USER.ID}
260
+ ${apiFields.$USER.EMAIL}
261
+ posts {
262
+ ${apiFields.$USER.$POSTS.TITLE}
263
+ }
264
+ }
265
+ }
266
+ `;
267
+
268
+ // REST with query params
269
+ fetch(
270
+ `/api/user?fields=${[
271
+ apiFields.$USER.EMAIL_FIELD,
272
+ apiFields.$USER.$POSTS.TITLE_FIELD,
273
+ ].join(',')}`,
274
+ );
275
+ ```
276
+
277
+ ## API Reference
278
+
279
+ ### `generateFields(schema)`
280
+
281
+ **Input:** Object schema where values are field names (strings), nested objects, or arrays.
282
+
283
+ **Output:** Generated field accessors with type safety.
284
+
285
+ #### Generated Properties
286
+
287
+ | Pattern | Type | Example |
288
+ | ------------------ | ---------------------- | ------------------------- |
289
+ | `FIELD_NAME` | `string` | `EMAIL` → `'email'` |
290
+ | `FIELD_NAME_FIELD` | `string` or `function` | `EMAIL_FIELD` → `'email'` |
291
+ | `$NESTED` | `object` | Nested field accessors |
292
+ | `KEY` | `string` | Original key name |
293
+ | `PATH` | `string` or `function` | Full path to field |
294
+
295
+ **Notes:**
296
+
297
+ - Field names convert to `SCREAMING_SNAKE_CASE`
298
+ - Nested objects prefix with `$`
299
+ - Array fields become functions accepting indices
300
+ - `_FIELD` suffix provides full path
301
+
302
+ ## TypeScript Support
303
+
304
+ Requires TypeScript 4.5+.
305
+
306
+ Full type inference and autocomplete:
307
+
308
+ ```typescript
309
+ const fields = generateFields({
310
+ user: {
311
+ name: 'name',
312
+ tags: [{ value: 'value' }],
313
+ },
314
+ });
315
+
316
+ // ✅ Valid - TypeScript knows these exist
317
+ fields.$USER.NAME_FIELD;
318
+ fields.$USER.$TAGS.VALUE_FIELD(0);
319
+
320
+ // ❌ Type error - property doesn't exist
321
+ fields.$USER.INVALID_FIELD;
322
+
323
+ // ❌ Type error - wrong arity
324
+ fields.$USER.$TAGS.VALUE_FIELD(); // Expected 1 argument
325
+ ```
326
+
327
+ ## Design Decisions
328
+
329
+ **Why `$` prefix for nested objects?**
330
+ Distinguishes between value constants (`EMAIL`) and nested accessors (`$PROFILE`). Makes structure immediately visible.
331
+
332
+ **Why functions for arrays?**
333
+ Arrays need dynamic indices. Functions provide type-safe, flexible access: `ITEMS_FIELD(index)`.
334
+
335
+ **Why both `NAME` and `NAME_FIELD`?**
336
+
337
+ - `NAME` - the value: `'name'` (useful for object keys)
338
+ - `NAME_FIELD` - the path: `'user.name'` (useful for form libraries)
339
+
340
+ **Why `SCREAMING_SNAKE_CASE`?**
341
+
342
+ - Distinguishes generated constants from regular variables
343
+ - Convention in many libraries (Redux actions, etc.)
344
+ - Easier to spot in large codebases
345
+
346
+ ## Performance
347
+
348
+ - **Zero runtime overhead** - pure type-level transformations
349
+ - **Tree-shakeable** - dead code elimination works perfectly
350
+ - **No dependencies** - ~2KB gzipped
351
+ - **Fast TypeScript compilation** - efficient recursive types
352
+
353
+ ## Patterns & Best Practices
354
+
355
+ ### Central Field Definitions
356
+
357
+ ```typescript
358
+ // src/fields/user.fields.ts
359
+ export const UserFields = generateFields({
360
+ id: 'id',
361
+ email: 'email',
362
+ profile: {
363
+ firstName: 'firstName',
364
+ lastName: 'lastName',
365
+ },
366
+ });
367
+
368
+ // Use everywhere
369
+ import { UserFields } from '@/fields/user.fields';
370
+ ```
371
+
372
+ ### Combining Multiple Schemas
373
+
374
+ ```typescript
375
+ const productFields = generateFields({
376
+ /* ... */
377
+ });
378
+ const orderFields = generateFields({
379
+ /* ... */
380
+ });
381
+
382
+ // Use separately or together
383
+ const allFields = { productFields, orderFields };
384
+ ```
385
+
386
+ ### Conditional Field Access
387
+
388
+ ```typescript
389
+ const fields = generateFields({
390
+ user: {
391
+ role: 'role',
392
+ adminSettings: { permission: 'permission' },
393
+ },
394
+ });
395
+
396
+ // Type-safe conditional access
397
+ const getSettingsPath = (isAdmin: boolean) =>
398
+ isAdmin ? fields.$USER.$ADMIN_SETTINGS.PERMISSION_FIELD : null;
399
+ ```
400
+
401
+ ### Testing
402
+
403
+ ```typescript
404
+ import { describe, it, expect } from 'vitest';
405
+
406
+ describe('UserFields', () => {
407
+ it('generates correct paths', () => {
408
+ expect(UserFields.EMAIL_FIELD).toBe('email');
409
+ expect(UserFields.$PROFILE.FIRST_NAME_FIELD).toBe('profile.firstName');
410
+ });
411
+
412
+ it('handles array indices', () => {
413
+ expect(UserFields.$ADDRESSES.STREET_FIELD(0)).toBe('addresses.0.street');
414
+ });
415
+ });
416
+ ```
417
+
418
+ ## Migration Guide
419
+
420
+ ### From Hardcoded Strings
421
+
422
+ ```typescript
423
+ // Before
424
+ const emailField = 'user.profile.email';
425
+ const addressField = (index) => `user.addresses.${index}.street`;
426
+
427
+ // After
428
+ const fields = generateFields({
429
+ user: {
430
+ profile: { email: 'email' },
431
+ addresses: [{ street: 'street' }],
432
+ },
433
+ });
434
+
435
+ const emailField = fields.$USER.$PROFILE.EMAIL_FIELD;
436
+ const addressField = (index) => fields.$USER.$ADDRESSES.STREET_FIELD(index);
437
+ ```
438
+
439
+ ### From Constants
440
+
441
+ ```typescript
442
+ // Before
443
+ export const FIELDS = {
444
+ EMAIL: 'email',
445
+ PROFILE_NAME: 'profile.name',
446
+ };
447
+
448
+ // After
449
+ export const FIELDS = generateFields({
450
+ email: 'email',
451
+ profile: { name: 'name' },
452
+ });
453
+ // Access: FIELDS.EMAIL, FIELDS.$PROFILE.NAME_FIELD
454
+ ```
455
+
456
+ ## Limitations
457
+
458
+ - Requires TypeScript 4.5+ for full type support
459
+ - Very deep nesting (10+ levels) may slow TypeScript compilation
460
+ - Arrays of primitives not supported (wrap in objects: `[{ value: string }]`)
461
+ - Dynamic keys not supported (must be known at compile time)
462
+
463
+ ## Troubleshooting
464
+
465
+ **TypeScript shows `any` type:**
466
+
467
+ - Ensure you're using TypeScript 4.5+
468
+ - Add `as const` to your schema: `generateFields({ ... } as const)`
469
+
470
+ **"Expected N arguments" error:**
471
+
472
+ - Check array nesting level
473
+ - Each array level adds one required index argument
474
+
475
+ **Slow compilation:**
476
+
477
+ - Reduce nesting depth
478
+ - Split large schemas into smaller pieces
479
+
480
+ ## Contributing
481
+
482
+ Contributions welcome! Please:
483
+
484
+ - Add tests for new features
485
+ - Update TypeScript types accordingly
486
+ - Follow existing code style
487
+ - Update documentation
488
+
489
+ ## License
490
+
491
+ MIT
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=convert.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"convert.test.d.ts","sourceRoot":"","sources":["../../src/__test__/convert.test.ts"],"names":[],"mappings":""}