@glitchproof/form-field-generator 1.0.3 → 1.0.5
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/LICENSE +21 -21
- package/README.md +502 -501
- package/dist/core/convert.d.ts.map +1 -1
- package/dist/core/convert.js.map +1 -1
- package/dist/index.js +0 -29
- package/dist/index.js.map +1 -1
- package/package.json +47 -46
- package/dist/__test__/convert.test.d.ts +0 -2
- package/dist/__test__/convert.test.d.ts.map +0 -1
- package/dist/__test__/convert.test.js +0 -429
- package/dist/__test__/convert.test.js.map +0 -1
- package/dist/__test__/create-index-formatter.test.d.ts +0 -2
- package/dist/__test__/create-index-formatter.test.d.ts.map +0 -1
- package/dist/__test__/create-index-formatter.test.js +0 -17
- package/dist/__test__/create-index-formatter.test.js.map +0 -1
- package/dist/__test__/is-list.test.d.ts +0 -2
- package/dist/__test__/is-list.test.d.ts.map +0 -1
- package/dist/__test__/is-list.test.js +0 -13
- package/dist/__test__/is-list.test.js.map +0 -1
- package/dist/__test__/to-snake-case.test.d.ts +0 -2
- package/dist/__test__/to-snake-case.test.d.ts.map +0 -1
- package/dist/__test__/to-snake-case.test.js +0 -14
- package/dist/__test__/to-snake-case.test.js.map +0 -1
package/README.md
CHANGED
|
@@ -1,501 +1,502 @@
|
|
|
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.
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
//
|
|
108
|
-
//
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
{
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
<input {...register(formFields.$PROFILE.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
-
|
|
148
|
-
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
[schema.
|
|
170
|
-
[schema
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
.
|
|
181
|
-
.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
[userFields.
|
|
203
|
-
[userFields
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
[stateFields.$
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
${apiFields.$USER.
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
apiFields.$USER
|
|
273
|
-
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
-
| `
|
|
291
|
-
|
|
|
292
|
-
| `
|
|
293
|
-
| `
|
|
294
|
-
| `
|
|
295
|
-
|
|
|
296
|
-
| | |
|
|
297
|
-
| | |
|
|
298
|
-
| | |
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
| | |
|
|
302
|
-
| | |
|
|
303
|
-
| | |
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
-
|
|
309
|
-
-
|
|
310
|
-
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
fields.$USER
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
- `
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
-
|
|
354
|
-
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
- **
|
|
360
|
-
- **
|
|
361
|
-
- **
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
expect(UserFields
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
});
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
const
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
-
|
|
470
|
-
-
|
|
471
|
-
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
-
|
|
496
|
-
-
|
|
497
|
-
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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.ELEMENT_AT(5); // users.5
|
|
91
|
+
fields.$USERS.KEY; // users
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Nested Arrays
|
|
95
|
+
|
|
96
|
+
Multiple indices for deeply nested arrays:
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
const fields = generateFields({
|
|
100
|
+
orders: [
|
|
101
|
+
{
|
|
102
|
+
items: [{ productId: 'productId', qty: 'qty' }],
|
|
103
|
+
},
|
|
104
|
+
],
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
fields.$ORDERS.$ITEMS.PRODUCT_ID_FIELD(0, 2); // 'orders.0.items.2.productId'
|
|
108
|
+
// ^ ^
|
|
109
|
+
// order item
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Real-World Usage
|
|
113
|
+
|
|
114
|
+
### React Hook Form
|
|
115
|
+
|
|
116
|
+
Stop hardcoding form field paths:
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { useForm } from 'react-hook-form';
|
|
120
|
+
|
|
121
|
+
const formFields = generateFields({
|
|
122
|
+
email: 'email',
|
|
123
|
+
password: 'password',
|
|
124
|
+
profile: {
|
|
125
|
+
firstName: 'firstName',
|
|
126
|
+
lastName: 'lastName'
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
function RegistrationForm() {
|
|
131
|
+
const { register, formState: { errors } } = useForm();
|
|
132
|
+
|
|
133
|
+
return (
|
|
134
|
+
<form>
|
|
135
|
+
<input {...register(formFields.EMAIL_FIELD)} />
|
|
136
|
+
{errors[formFields.EMAIL] && <span>Email required</span>}
|
|
137
|
+
|
|
138
|
+
<input {...register(formFields.$PROFILE.FIRST_NAME_FIELD)} />
|
|
139
|
+
<input {...register(formFields.$PROFILE.LAST_NAME_FIELD)} />
|
|
140
|
+
</form>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
**Benefits:**
|
|
146
|
+
|
|
147
|
+
- Rename `firstName` → `givenName`? Change once in schema, works everywhere
|
|
148
|
+
- TypeScript autocomplete guides you
|
|
149
|
+
- Impossible to typo field names
|
|
150
|
+
|
|
151
|
+
### Database Queries
|
|
152
|
+
|
|
153
|
+
Build type-safe query builders:
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const schema = generateFields({
|
|
157
|
+
id: 'id',
|
|
158
|
+
title: 'title',
|
|
159
|
+
author: {
|
|
160
|
+
name: 'name',
|
|
161
|
+
email: 'email',
|
|
162
|
+
},
|
|
163
|
+
tags: [{ name: 'name' }],
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Prisma-style
|
|
167
|
+
db.posts.findMany({
|
|
168
|
+
select: {
|
|
169
|
+
[schema.ID]: true,
|
|
170
|
+
[schema.TITLE]: true,
|
|
171
|
+
[schema.$AUTHOR.NAME]: true,
|
|
172
|
+
},
|
|
173
|
+
where: {
|
|
174
|
+
[schema.$AUTHOR.EMAIL_FIELD]: 'user@example.com',
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// SQL builder
|
|
179
|
+
query()
|
|
180
|
+
.select(schema.TITLE_FIELD)
|
|
181
|
+
.where(schema.$AUTHOR.NAME_FIELD, '=', 'John')
|
|
182
|
+
.orderBy(schema.ID_FIELD);
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Validation Schemas
|
|
186
|
+
|
|
187
|
+
Stop duplicating field paths:
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
import { z } from 'zod';
|
|
191
|
+
|
|
192
|
+
const userFields = generateFields({
|
|
193
|
+
email: 'email',
|
|
194
|
+
password: 'password',
|
|
195
|
+
profile: {
|
|
196
|
+
age: 'age',
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Define validation once
|
|
201
|
+
const schema = z.object({
|
|
202
|
+
[userFields.EMAIL]: z.string().email(),
|
|
203
|
+
[userFields.PASSWORD]: z.string().min(8),
|
|
204
|
+
[userFields.$PROFILE.AGE]: z.number().min(18),
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Use in forms, API validation, etc.
|
|
208
|
+
schema.parse(formData);
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### State Management
|
|
212
|
+
|
|
213
|
+
Type-safe selectors and reducers:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
const stateFields = generateFields({
|
|
217
|
+
user: {
|
|
218
|
+
profile: { name: 'name' },
|
|
219
|
+
preferences: { theme: 'theme' },
|
|
220
|
+
},
|
|
221
|
+
session: {
|
|
222
|
+
token: 'token',
|
|
223
|
+
expiresAt: 'expiresAt',
|
|
224
|
+
},
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Redux selectors
|
|
228
|
+
const selectUserName = (state) =>
|
|
229
|
+
state[stateFields.$USER.$PROFILE.KEY][stateFields.$USER.$PROFILE.NAME];
|
|
230
|
+
|
|
231
|
+
// Zustand
|
|
232
|
+
const useStore = create((set) => ({
|
|
233
|
+
[stateFields.$USER.KEY]: {},
|
|
234
|
+
[stateFields.$SESSION.KEY]: {},
|
|
235
|
+
}));
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
### API Field Selection
|
|
239
|
+
|
|
240
|
+
Control exactly what data you fetch:
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
const apiFields = generateFields({
|
|
244
|
+
user: {
|
|
245
|
+
id: 'id',
|
|
246
|
+
email: 'email',
|
|
247
|
+
posts: [
|
|
248
|
+
{
|
|
249
|
+
title: 'title',
|
|
250
|
+
comments: [{ text: 'text' }],
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// GraphQL
|
|
257
|
+
const query = gql`
|
|
258
|
+
query {
|
|
259
|
+
user {
|
|
260
|
+
${apiFields.$USER.ID}
|
|
261
|
+
${apiFields.$USER.EMAIL}
|
|
262
|
+
posts {
|
|
263
|
+
${apiFields.$USER.$POSTS.TITLE}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
`;
|
|
268
|
+
|
|
269
|
+
// REST with query params
|
|
270
|
+
fetch(
|
|
271
|
+
`/api/user?fields=${[
|
|
272
|
+
apiFields.$USER.EMAIL_FIELD,
|
|
273
|
+
apiFields.$USER.$POSTS.TITLE_FIELD,
|
|
274
|
+
].join(',')}`,
|
|
275
|
+
);
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
## API Reference
|
|
279
|
+
|
|
280
|
+
### `generateFields(schema)`
|
|
281
|
+
|
|
282
|
+
**Input:** Object schema where values are field names (strings), nested objects, or arrays.
|
|
283
|
+
|
|
284
|
+
**Output:** Generated field accessors with type safety.
|
|
285
|
+
|
|
286
|
+
#### Generated Properties
|
|
287
|
+
|
|
288
|
+
| Pattern | Type | Example |
|
|
289
|
+
| ------------------ | ---------------------- | --------------------------- |
|
|
290
|
+
| `FIELD_NAME` | `string` | `EMAIL` → `'email'` |
|
|
291
|
+
| `FIELD_NAME_FIELD` | `string` or `function` | `EMAIL_FIELD` → `'email'` |
|
|
292
|
+
| `$NESTED` | `object` | Nested field accessors |
|
|
293
|
+
| `KEY` | `string` | Original key name |
|
|
294
|
+
| `PATH` | `string` or `function` | Full path to field |
|
|
295
|
+
| `AT` | `function` | Only listed object fields |
|
|
296
|
+
| | | have this method to get |
|
|
297
|
+
| | | object path from nested or |
|
|
298
|
+
| | | list inside |
|
|
299
|
+
| | | |
|
|
300
|
+
| `ELEMENT_AT` | `function` | Only array fields has |
|
|
301
|
+
| | | array fields to get array |
|
|
302
|
+
| | | specific element like |
|
|
303
|
+
| | | fields.$USERS.ELEMENT_AT(3) |
|
|
304
|
+
| | | provide path `users.3` |
|
|
305
|
+
|
|
306
|
+
**Notes:**
|
|
307
|
+
|
|
308
|
+
- Field names convert to `SCREAMING_SNAKE_CASE`
|
|
309
|
+
- Nested objects prefix with `$`
|
|
310
|
+
- Array fields become functions accepting indices
|
|
311
|
+
- `_FIELD` suffix provides full path
|
|
312
|
+
|
|
313
|
+
## TypeScript Support
|
|
314
|
+
|
|
315
|
+
Requires TypeScript 4.5+.
|
|
316
|
+
|
|
317
|
+
Full type inference and autocomplete:
|
|
318
|
+
|
|
319
|
+
```typescript
|
|
320
|
+
const fields = generateFields({
|
|
321
|
+
user: {
|
|
322
|
+
name: 'name',
|
|
323
|
+
tags: [{ value: 'value' }],
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// ✅ Valid - TypeScript knows these exist
|
|
328
|
+
fields.$USER.NAME_FIELD;
|
|
329
|
+
fields.$USER.$TAGS.VALUE_FIELD(0);
|
|
330
|
+
|
|
331
|
+
// ❌ Type error - property doesn't exist
|
|
332
|
+
fields.$USER.INVALID_FIELD;
|
|
333
|
+
|
|
334
|
+
// ❌ Type error - wrong arity
|
|
335
|
+
fields.$USER.$TAGS.VALUE_FIELD(); // Expected 1 argument
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Design Decisions
|
|
339
|
+
|
|
340
|
+
**Why `$` prefix for nested objects?**
|
|
341
|
+
Distinguishes between value constants (`EMAIL`) and nested accessors (`$PROFILE`). Makes structure immediately visible.
|
|
342
|
+
|
|
343
|
+
**Why functions for arrays?**
|
|
344
|
+
Arrays need dynamic indices. Functions provide type-safe, flexible access: `ITEMS_FIELD(index)`.
|
|
345
|
+
|
|
346
|
+
**Why both `NAME` and `NAME_FIELD`?**
|
|
347
|
+
|
|
348
|
+
- `NAME` - the value: `'name'` (useful for object keys)
|
|
349
|
+
- `NAME_FIELD` - the path: `'user.name'` (useful for form libraries)
|
|
350
|
+
|
|
351
|
+
**Why `SCREAMING_SNAKE_CASE`?**
|
|
352
|
+
|
|
353
|
+
- Distinguishes generated constants from regular variables
|
|
354
|
+
- Convention in many libraries (Redux actions, etc.)
|
|
355
|
+
- Easier to spot in large codebases
|
|
356
|
+
|
|
357
|
+
## Performance
|
|
358
|
+
|
|
359
|
+
- **Zero runtime overhead** - pure type-level transformations
|
|
360
|
+
- **Tree-shakeable** - dead code elimination works perfectly
|
|
361
|
+
- **No dependencies** - ~10KB gzipped
|
|
362
|
+
- **Fast TypeScript compilation** - efficient recursive types
|
|
363
|
+
|
|
364
|
+
## Patterns & Best Practices
|
|
365
|
+
|
|
366
|
+
### Central Field Definitions
|
|
367
|
+
|
|
368
|
+
```typescript
|
|
369
|
+
// src/fields/user.fields.ts
|
|
370
|
+
export const UserFields = generateFields({
|
|
371
|
+
id: 'id',
|
|
372
|
+
email: 'email',
|
|
373
|
+
profile: {
|
|
374
|
+
firstName: 'firstName',
|
|
375
|
+
lastName: 'lastName',
|
|
376
|
+
},
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
// Use everywhere
|
|
380
|
+
import { UserFields } from '@/fields/user.fields';
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
### Combining Multiple Schemas
|
|
384
|
+
|
|
385
|
+
```typescript
|
|
386
|
+
const productFields = generateFields({
|
|
387
|
+
/* ... */
|
|
388
|
+
});
|
|
389
|
+
const orderFields = generateFields({
|
|
390
|
+
/* ... */
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
// Use separately or together
|
|
394
|
+
const allFields = { productFields, orderFields };
|
|
395
|
+
```
|
|
396
|
+
|
|
397
|
+
### Conditional Field Access
|
|
398
|
+
|
|
399
|
+
```typescript
|
|
400
|
+
const fields = generateFields({
|
|
401
|
+
user: {
|
|
402
|
+
role: 'role',
|
|
403
|
+
adminSettings: { permission: 'permission' },
|
|
404
|
+
},
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
// Type-safe conditional access
|
|
408
|
+
const getSettingsPath = (isAdmin: boolean) =>
|
|
409
|
+
isAdmin ? fields.$USER.$ADMIN_SETTINGS.PERMISSION_FIELD : null;
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
### Testing
|
|
413
|
+
|
|
414
|
+
```typescript
|
|
415
|
+
import { describe, it, expect } from 'vitest';
|
|
416
|
+
|
|
417
|
+
describe('UserFields', () => {
|
|
418
|
+
it('generates correct paths', () => {
|
|
419
|
+
expect(UserFields.EMAIL_FIELD).toBe('email');
|
|
420
|
+
expect(UserFields.$PROFILE.FIRST_NAME_FIELD).toBe('profile.firstName');
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
it('handles array indices', () => {
|
|
424
|
+
expect(UserFields.$ADDRESSES.STREET_FIELD(0)).toBe('addresses.0.street');
|
|
425
|
+
});
|
|
426
|
+
});
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
## Migration Guide
|
|
430
|
+
|
|
431
|
+
### From Hardcoded Strings
|
|
432
|
+
|
|
433
|
+
```typescript
|
|
434
|
+
// Before
|
|
435
|
+
const emailField = 'user.profile.email';
|
|
436
|
+
const addressField = (index) => `user.addresses.${index}.street`;
|
|
437
|
+
|
|
438
|
+
// After
|
|
439
|
+
const fields = generateFields({
|
|
440
|
+
user: {
|
|
441
|
+
profile: { email: 'email' },
|
|
442
|
+
addresses: [{ street: 'street' }],
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const emailField = fields.$USER.$PROFILE.EMAIL_FIELD;
|
|
447
|
+
const addressField = (index) => fields.$USER.$ADDRESSES.STREET_FIELD(index);
|
|
448
|
+
```
|
|
449
|
+
|
|
450
|
+
### From Constants
|
|
451
|
+
|
|
452
|
+
```typescript
|
|
453
|
+
// Before
|
|
454
|
+
export const FIELDS = {
|
|
455
|
+
EMAIL: 'email',
|
|
456
|
+
PROFILE_NAME: 'profile.name',
|
|
457
|
+
};
|
|
458
|
+
|
|
459
|
+
// After
|
|
460
|
+
export const FIELDS = generateFields({
|
|
461
|
+
email: 'email',
|
|
462
|
+
profile: { name: 'name' },
|
|
463
|
+
});
|
|
464
|
+
// Access: FIELDS.EMAIL, FIELDS.$PROFILE.NAME_FIELD
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
## Limitations
|
|
468
|
+
|
|
469
|
+
- Requires TypeScript 4.5+ for full type support
|
|
470
|
+
- Very deep nesting (10+ levels) may slow TypeScript compilation
|
|
471
|
+
- Arrays of primitives not supported (wrap in objects: `[{ value: string }]`)
|
|
472
|
+
- Dynamic keys not supported (must be known at compile time)
|
|
473
|
+
|
|
474
|
+
## Troubleshooting
|
|
475
|
+
|
|
476
|
+
**TypeScript shows `any` type:**
|
|
477
|
+
|
|
478
|
+
- Ensure you're using TypeScript 4.5+
|
|
479
|
+
- Add `as const` to your schema: `generateFields({ ... } as const)`
|
|
480
|
+
|
|
481
|
+
**"Expected N arguments" error:**
|
|
482
|
+
|
|
483
|
+
- Check array nesting level
|
|
484
|
+
- Each array level adds one required index argument
|
|
485
|
+
|
|
486
|
+
**Slow compilation:**
|
|
487
|
+
|
|
488
|
+
- Reduce nesting depth
|
|
489
|
+
- Split large schemas into smaller pieces
|
|
490
|
+
|
|
491
|
+
## Contributing
|
|
492
|
+
|
|
493
|
+
Contributions welcome! Please:
|
|
494
|
+
|
|
495
|
+
- Add tests for new features
|
|
496
|
+
- Update TypeScript types accordingly
|
|
497
|
+
- Follow existing code style
|
|
498
|
+
- Update documentation
|
|
499
|
+
|
|
500
|
+
## License
|
|
501
|
+
|
|
502
|
+
MIT
|