sanity-advanced-validators 0.4.0 → 0.5.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 +176 -77
- package/dist/index.cjs +26 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +25 -15
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -9,11 +9,13 @@ This package includes a set of Sanity validators for aggressive and weird edge c
|
|
|
9
9
|
- [fileExtension](#fileExtension)
|
|
10
10
|
- [minDimensions](#minDimensions)
|
|
11
11
|
- [maxDimensions](#maxDimensions)
|
|
12
|
+
- [minCount](#minCount) 🆕
|
|
13
|
+
- [maxCount](#maxCount) 🆕
|
|
12
14
|
- [requiredIfSiblingEq](#requiredIfSiblingEq)
|
|
13
15
|
- [requiredIfSiblingNeq](#requiredIfSiblingNeq)
|
|
14
16
|
- [requiredIfSlugEq](#requiredIfSlugEq)
|
|
15
17
|
- [requiredIfSlugNeq](#requiredIfSlugNeq)
|
|
16
|
-
- [regex](#regex)
|
|
18
|
+
- [regex](#regex)
|
|
17
19
|
- [referencedDocumentRequires](#referencedDocumentRequires)
|
|
18
20
|
- [maxDepth](#maxDepth)
|
|
19
21
|
|
|
@@ -38,17 +40,27 @@ const Page = defineType({
|
|
|
38
40
|
name: "someVideoFile",
|
|
39
41
|
type: "file",
|
|
40
42
|
validation: (rule) =>
|
|
41
|
-
rule.custom(
|
|
42
|
-
|
|
43
|
+
rule.custom(
|
|
44
|
+
requiredIfSlugEq(
|
|
45
|
+
'about',
|
|
46
|
+
'A video is required if {slugKey} is {operand}.'
|
|
47
|
+
)
|
|
48
|
+
).custom(
|
|
49
|
+
fileExtension(['mp4', 'mov'])
|
|
50
|
+
)
|
|
43
51
|
})
|
|
44
52
|
defineField({
|
|
45
53
|
name: "posterImage",
|
|
46
54
|
type: "image",
|
|
47
55
|
hidden: ({ parent }) => parent.someVideoFile === null,
|
|
48
56
|
validation: (rule) =>
|
|
49
|
-
rule.custom(
|
|
50
|
-
|
|
51
|
-
|
|
57
|
+
rule.custom(
|
|
58
|
+
requiredIfSiblingNeq('someVideoFile', null)
|
|
59
|
+
).custom(
|
|
60
|
+
minDimensions({ x: 1250, y: 800 })
|
|
61
|
+
).custom(
|
|
62
|
+
maxDimensions({ x: 2500, y: 1600 })
|
|
63
|
+
),
|
|
52
64
|
})
|
|
53
65
|
]
|
|
54
66
|
})
|
|
@@ -75,12 +87,18 @@ const Page = defineType({
|
|
|
75
87
|
defineField({
|
|
76
88
|
name: "catalog",
|
|
77
89
|
type: "file",
|
|
78
|
-
validation: (rule) =>
|
|
90
|
+
validation: (rule) =>
|
|
91
|
+
rule.custom(
|
|
92
|
+
fileExtension("pdf")
|
|
93
|
+
),
|
|
79
94
|
}),
|
|
80
95
|
defineField({
|
|
81
96
|
name: "video",
|
|
82
97
|
type: "file",
|
|
83
|
-
validation: (rule) =>
|
|
98
|
+
validation: (rule) =>
|
|
99
|
+
rule.custom(
|
|
100
|
+
fileExtension(["mp4", "mov", "webm"])
|
|
101
|
+
),
|
|
84
102
|
}),
|
|
85
103
|
],
|
|
86
104
|
})
|
|
@@ -108,7 +126,10 @@ const ImageWithCaption = defineType({
|
|
|
108
126
|
defineField({
|
|
109
127
|
name: "heroImage",
|
|
110
128
|
type: "image",
|
|
111
|
-
validation: (rule) =>
|
|
129
|
+
validation: (rule) =>
|
|
130
|
+
rule.custom(
|
|
131
|
+
minDimensions({ x: 1200, y: 800 })
|
|
132
|
+
),
|
|
112
133
|
}),
|
|
113
134
|
],
|
|
114
135
|
})
|
|
@@ -129,7 +150,10 @@ message?: string // optional custom error message; replaces {x} and {y} with you
|
|
|
129
150
|
defineField({
|
|
130
151
|
name: "heroImage",
|
|
131
152
|
type: "image",
|
|
132
|
-
validation: (rule) =>
|
|
153
|
+
validation: (rule) =>
|
|
154
|
+
rule.custom(
|
|
155
|
+
maxDimensions({ x: 2400, y: 1600 })
|
|
156
|
+
),
|
|
133
157
|
}),
|
|
134
158
|
```
|
|
135
159
|
|
|
@@ -141,15 +165,66 @@ defineField({
|
|
|
141
165
|
type: "image",
|
|
142
166
|
description: "Min: 1200x800, max: 2400x1600.",
|
|
143
167
|
validation: (rule) =>
|
|
144
|
-
rule
|
|
145
|
-
.
|
|
146
|
-
|
|
147
|
-
.custom(
|
|
168
|
+
rule.required()
|
|
169
|
+
.custom(
|
|
170
|
+
minDimensions({ x: 1200, y: 800 })
|
|
171
|
+
).custom(
|
|
172
|
+
maxDimensions({ x: 2400, y: 1600 })
|
|
173
|
+
),
|
|
148
174
|
})
|
|
149
175
|
```
|
|
150
176
|
|
|
151
177
|
---
|
|
152
178
|
|
|
179
|
+
### minCount
|
|
180
|
+
|
|
181
|
+
Enforces that an array contains at least _n_ items.
|
|
182
|
+
|
|
183
|
+
Note that null values are fine; use `rule.required()` to enforce non-nulls.
|
|
184
|
+
|
|
185
|
+
```typescript
|
|
186
|
+
n: number,
|
|
187
|
+
message?: string // optional custom error message; replaces {n} with your minimum count
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
```typescript
|
|
191
|
+
defineField({
|
|
192
|
+
name: "thumbnails",
|
|
193
|
+
type: "array",
|
|
194
|
+
of: [ {type: 'image'} ],
|
|
195
|
+
validation: (rule) =>
|
|
196
|
+
rule.required()
|
|
197
|
+
.custom(
|
|
198
|
+
minCount(3, "At least {n} thumbnails are required.")
|
|
199
|
+
),
|
|
200
|
+
}),
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
### maxCount
|
|
206
|
+
|
|
207
|
+
Enforces that an array contains at most _n_ items.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
n: number,
|
|
211
|
+
message?: string // optional custom error message; replaces {n} with your maximum count
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
defineField({
|
|
216
|
+
name: "thumbnails",
|
|
217
|
+
type: "array",
|
|
218
|
+
of: [ {type: 'image'} ],
|
|
219
|
+
validation: (rule) =>
|
|
220
|
+
rule.custom(
|
|
221
|
+
maxCount(3, "No more than {n} thumbnails.")
|
|
222
|
+
),
|
|
223
|
+
}),
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
153
228
|
### requiredIfSiblingEq
|
|
154
229
|
|
|
155
230
|
Mark a field as `required` if a sibling field has a particular value. This is the validator we use most. _It’s super effective!_
|
|
@@ -160,7 +235,7 @@ _note:_ This does not work for slugs, because they have to match a nested `.curr
|
|
|
160
235
|
|
|
161
236
|
```typescript
|
|
162
237
|
key: string, // name of sibling
|
|
163
|
-
operand: string | number | null | Array<string, number
|
|
238
|
+
operand: string | number | boolean | null | Array<string, number> // value that you’re testing for (i.e. if 'name' === operand)
|
|
164
239
|
message?: string // optional custom error message; replaces {key} and {operand} with your input, and {siblingValue} with the value of the sibling you’re testing against.
|
|
165
240
|
```
|
|
166
241
|
|
|
@@ -187,73 +262,77 @@ defineType({
|
|
|
187
262
|
type: 'string',
|
|
188
263
|
options: {
|
|
189
264
|
list: [
|
|
190
|
-
'
|
|
265
|
+
'typescript', 'rust', 'python', 'swift'
|
|
191
266
|
]
|
|
192
267
|
},
|
|
193
|
-
validation: rule =>
|
|
268
|
+
validation: rule =>
|
|
269
|
+
rule.custom(
|
|
270
|
+
requiredIfSiblingEq('occupation', 'software engineer')
|
|
271
|
+
),
|
|
194
272
|
hidden: ({parent}) => parent.occuption !== 'software engineer',
|
|
195
273
|
}),
|
|
196
274
|
],
|
|
197
275
|
})
|
|
198
276
|
```
|
|
199
277
|
|
|
200
|
-
|
|
278
|
+
And it also works for arrays.
|
|
201
279
|
|
|
202
280
|
```typescript
|
|
203
281
|
defineType({
|
|
204
|
-
name:
|
|
205
|
-
type:
|
|
282
|
+
name: "person",
|
|
283
|
+
type: "object",
|
|
206
284
|
fields: [
|
|
285
|
+
// ...
|
|
207
286
|
defineField({
|
|
208
|
-
name:
|
|
209
|
-
type:
|
|
287
|
+
name: "occupation",
|
|
288
|
+
type: "string",
|
|
289
|
+
options: {
|
|
290
|
+
list: ["doctor", "lawyer", "software engineer", "linguist"],
|
|
291
|
+
},
|
|
210
292
|
}),
|
|
211
293
|
defineField({
|
|
212
|
-
name:
|
|
213
|
-
type:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
294
|
+
name: "favoriteLanguage",
|
|
295
|
+
type: "string",
|
|
296
|
+
options: {
|
|
297
|
+
list: ["typescript", "rust", "python", "swift", "latin", "urdu", "klingon"],
|
|
298
|
+
},
|
|
299
|
+
validation: (rule) =>
|
|
300
|
+
rule.custom(
|
|
301
|
+
requiredIfSiblingEq("occupation", ["software engineer", "linguist"])
|
|
302
|
+
),
|
|
303
|
+
hidden: ({ parent }) => !["software engineer", "linguist"].includes(parent.occupation),
|
|
304
|
+
}),
|
|
224
305
|
],
|
|
225
306
|
})
|
|
226
307
|
```
|
|
227
308
|
|
|
228
|
-
|
|
309
|
+
It even works for null.
|
|
229
310
|
|
|
230
311
|
```typescript
|
|
231
312
|
defineType({
|
|
232
|
-
name:
|
|
233
|
-
type:
|
|
313
|
+
name: 'person',
|
|
314
|
+
type: 'object',
|
|
234
315
|
fields: [
|
|
235
316
|
defineField({
|
|
236
|
-
name:
|
|
237
|
-
type:
|
|
238
|
-
}),
|
|
239
|
-
defineField({
|
|
240
|
-
name: "name",
|
|
241
|
-
type: "string",
|
|
317
|
+
name: 'name',
|
|
318
|
+
type: 'string'
|
|
242
319
|
}),
|
|
243
320
|
defineField({
|
|
244
|
-
name:
|
|
245
|
-
type:
|
|
246
|
-
|
|
247
|
-
list: ["doctor", "lawyer", "software engineer"],
|
|
248
|
-
},
|
|
249
|
-
}),
|
|
321
|
+
name: 'email',
|
|
322
|
+
type: 'string',
|
|
323
|
+
})
|
|
250
324
|
defineField({
|
|
251
|
-
name:
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
325
|
+
name: 'phone',
|
|
326
|
+
type: 'string',
|
|
327
|
+
validation: rule =>
|
|
328
|
+
rule.custom(
|
|
329
|
+
requiredIfSiblingEq(
|
|
330
|
+
'email',
|
|
331
|
+
null,
|
|
332
|
+
"If you don’t have an email address, a phone number is required."
|
|
333
|
+
)
|
|
334
|
+
)
|
|
335
|
+
})
|
|
257
336
|
],
|
|
258
337
|
})
|
|
259
338
|
```
|
|
@@ -262,13 +341,13 @@ defineType({
|
|
|
262
341
|
|
|
263
342
|
### requiredIfSiblingNeq
|
|
264
343
|
|
|
265
|
-
For a given object that has multiple fields, mark a field as `required` if a sibling does _not_ have a particular value.
|
|
344
|
+
For a given object that has multiple fields, mark a field as `required` if a sibling does _not_ have a particular value (or member of an array of values).
|
|
266
345
|
|
|
267
346
|
_note:_ This does not work for slugs, because they have to match a nested `.current` value. Use the [requiredIfSlugNeq validator](#requiredIfSlugNeq) instead.
|
|
268
347
|
|
|
269
348
|
```typescript
|
|
270
349
|
key: string, // name of sibling
|
|
271
|
-
operand: string | number | null | Array<string, number
|
|
350
|
+
operand: string | number | boolean | null | Array<string, number> // value that you’re testing for (i.e. if 'name' === operand)
|
|
272
351
|
message?: string // optional custom error message; replaces {key} and {operand} with your input, and {siblingValue} with the value of the sibling you’re testing against.
|
|
273
352
|
```
|
|
274
353
|
|
|
@@ -291,12 +370,15 @@ defineType({
|
|
|
291
370
|
}
|
|
292
371
|
})
|
|
293
372
|
defineField({
|
|
294
|
-
name:
|
|
295
|
-
description:
|
|
296
|
-
type:
|
|
297
|
-
validation: rule =>
|
|
298
|
-
|
|
299
|
-
|
|
373
|
+
name: "explanation",
|
|
374
|
+
description: "Why are you wasting your life this way?",
|
|
375
|
+
type: "text",
|
|
376
|
+
validation: (rule) =>
|
|
377
|
+
rule.custom(
|
|
378
|
+
requiredIfSiblingNeq("occupation", "software engineer")
|
|
379
|
+
),
|
|
380
|
+
hidden: ({ parent }) => parent.occuption === "software engineer",
|
|
381
|
+
}), ],
|
|
300
382
|
})
|
|
301
383
|
```
|
|
302
384
|
|
|
@@ -307,7 +389,7 @@ defineType({
|
|
|
307
389
|
Mark a field as `required` for documents with matching slugs.
|
|
308
390
|
|
|
309
391
|
```typescript
|
|
310
|
-
operand: string | number | null | Array<string, number
|
|
392
|
+
operand: string | number | null | Array<string, number> // possible slug or slugs you’re testing
|
|
311
393
|
key?: string, // name of sibling if not "slug"
|
|
312
394
|
message?: string // optional custom error message; replaces {slugKey} and {operand} with your input, and {siblingSlugValue} with the value of the sibling you’re testing against.
|
|
313
395
|
```
|
|
@@ -327,7 +409,10 @@ defineType({
|
|
|
327
409
|
name: "questionsAndAnswers",
|
|
328
410
|
type: "array",
|
|
329
411
|
of: [{ type: "qaItem" }],
|
|
330
|
-
validation: (rule) =>
|
|
412
|
+
validation: (rule) =>
|
|
413
|
+
rule.custom(
|
|
414
|
+
requiredIfSlugEq("faq")
|
|
415
|
+
),
|
|
331
416
|
hidden: ({ parent }) => parent.slug.current !== "faq",
|
|
332
417
|
}),
|
|
333
418
|
],
|
|
@@ -339,7 +424,10 @@ And this can apply to multiple slugs…
|
|
|
339
424
|
```typescript
|
|
340
425
|
defineField({
|
|
341
426
|
name: "questionsAndAnswers",
|
|
342
|
-
validation: (rule) =>
|
|
427
|
+
validation: (rule) =>
|
|
428
|
+
rule.custom(
|
|
429
|
+
requiredIfSlugEq(["faq", "about"])
|
|
430
|
+
),
|
|
343
431
|
}),
|
|
344
432
|
```
|
|
345
433
|
|
|
@@ -350,7 +438,7 @@ defineField({
|
|
|
350
438
|
Require fields on pages that don't match one or more slugs.
|
|
351
439
|
|
|
352
440
|
```typescript
|
|
353
|
-
operand: string | number | null | Array<string, number
|
|
441
|
+
operand: string | number | null | Array<string, number> // possible slug or slugs you’re testing
|
|
354
442
|
key?: string, // name of sibling if not "slug"
|
|
355
443
|
message?: string // optional custom error message; replaces {slugKey} and {operand} with your input, and {siblingSlugValue} with the value of the sibling you’re testing against.
|
|
356
444
|
```
|
|
@@ -371,7 +459,10 @@ defineType({
|
|
|
371
459
|
description: `Subnav is required on documents that aren’t '/home'`,
|
|
372
460
|
type: "array",
|
|
373
461
|
of: [{ type: "navLink" }],
|
|
374
|
-
validation: (rule) =>
|
|
462
|
+
validation: (rule) =>
|
|
463
|
+
rule.custom(
|
|
464
|
+
requiredIfSlugNeq("home")
|
|
465
|
+
),
|
|
375
466
|
hidden: ({ parent }) => parent.slug.current !== "home",
|
|
376
467
|
}),
|
|
377
468
|
],
|
|
@@ -395,16 +486,18 @@ message?: string // optional custom error message; replaces {pattern} with your
|
|
|
395
486
|
defineField({
|
|
396
487
|
name: 'email',
|
|
397
488
|
type: 'string',
|
|
398
|
-
validation: (rule) =>
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
489
|
+
validation: (rule) =>
|
|
490
|
+
rule.custom(
|
|
491
|
+
regex(
|
|
492
|
+
/^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})*$/,
|
|
493
|
+
"“{value}” is not a valid email address."
|
|
494
|
+
)
|
|
495
|
+
),
|
|
404
496
|
}),
|
|
405
497
|
```
|
|
406
498
|
|
|
407
499
|
**Custom error messages are highly recommended here.** Without the custom message above, the default response would be:
|
|
500
|
+
|
|
408
501
|
```
|
|
409
502
|
“me@googlecom” does not match the pattern /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,6})*$/.
|
|
410
503
|
```
|
|
@@ -427,7 +520,10 @@ defineField({
|
|
|
427
520
|
description: 'An article (must include a valid poster image)',
|
|
428
521
|
type: 'reference',
|
|
429
522
|
to: [{type: 'article'}],
|
|
430
|
-
validation: (rule) =>
|
|
523
|
+
validation: (rule) =>
|
|
524
|
+
rule.custom(
|
|
525
|
+
referencedDocumentRequires('article', 'poster')
|
|
526
|
+
),
|
|
431
527
|
}),
|
|
432
528
|
```
|
|
433
529
|
|
|
@@ -503,7 +599,10 @@ const navLink = defineType({
|
|
|
503
599
|
name: "subnav",
|
|
504
600
|
type: "array",
|
|
505
601
|
of: [{ type: navigation }],
|
|
506
|
-
validation: (rule) =>
|
|
602
|
+
validation: (rule) =>
|
|
603
|
+
rule.custom(
|
|
604
|
+
maxDepth(3, "subnav")
|
|
605
|
+
),
|
|
507
606
|
}),
|
|
508
607
|
],
|
|
509
608
|
})
|
|
@@ -536,7 +635,7 @@ defineField({
|
|
|
536
635
|
|
|
537
636
|
Most of these validators rely on a function called `getSibling()`. If you’re thinking about picking this apart and writing your own custom validator, take a close look at how these validators use it.
|
|
538
637
|
|
|
539
|
-
##
|
|
638
|
+
## Roadmap
|
|
540
639
|
|
|
541
640
|
### Nested pathfinders
|
|
542
641
|
|
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ __export(index_exports, {
|
|
|
25
25
|
maxDepth: () => maxDepth,
|
|
26
26
|
minDimensions: () => minDimensions,
|
|
27
27
|
referencedDocumentRequires: () => referencedDocumentRequires,
|
|
28
|
+
regex: () => regex,
|
|
28
29
|
requiredIfSiblingEq: () => requiredIfSiblingEq,
|
|
29
30
|
requiredIfSlugEq: () => requiredIfSlugEq
|
|
30
31
|
});
|
|
@@ -69,20 +70,20 @@ var minDimensions = ({ x, y }, message) => (value) => {
|
|
|
69
70
|
}
|
|
70
71
|
const { width, height } = (0, import_asset_utils2.getImageDimensions)(value.asset._ref);
|
|
71
72
|
if (!!x && width < x) {
|
|
72
|
-
return message ? message.replace("{x}", x.toString()).replace("{y}", !y ? "(any)" : y.toString()) : `Image must be at least ${x} pixels wide.`;
|
|
73
|
+
return message ? message.replace("{width}", width.toString()).replace("{height}", height.toString()).replace("{x}", x.toString()).replace("{y}", !y ? "(any)" : y.toString()) : `Image must be at least ${x} pixels wide.`;
|
|
73
74
|
}
|
|
74
75
|
if (!!y && height < y) {
|
|
75
|
-
return message ? message.replace("{x}", !x ? "(any)" : x.toString()).replace("{y}", y.toString()) : `Image must be at least ${y} pixels tall.`;
|
|
76
|
+
return message ? message.replace("{width}", width.toString()).replace("{height}", height.toString()).replace("{x}", !x ? "(any)" : x.toString()).replace("{y}", y.toString()) : `Image must be at least ${y} pixels tall.`;
|
|
76
77
|
}
|
|
77
78
|
return true;
|
|
78
79
|
};
|
|
79
80
|
|
|
80
81
|
// src/maxDepth.ts
|
|
81
|
-
var maxDepth = (maxDepth2,
|
|
82
|
-
let
|
|
83
|
-
const paths = context.path.filter((e) => typeof e === "string" && e.match(
|
|
82
|
+
var maxDepth = (maxDepth2, key, message = `Error: You can only nest {key} {maxDepth} levels deep.`) => (_, context) => {
|
|
83
|
+
let regex2 = new RegExp(String.raw`topLevelItems|${key}`);
|
|
84
|
+
const paths = context.path.filter((e) => typeof e === "string" && e.match(regex2));
|
|
84
85
|
if (paths.length > maxDepth2) {
|
|
85
|
-
return message.replace("{nestedValueName}",
|
|
86
|
+
return message.replace("{key}", key).replace("{nestedValueName}", key).replace("{maxDepth}", maxDepth2.toString());
|
|
86
87
|
}
|
|
87
88
|
return true;
|
|
88
89
|
};
|
|
@@ -91,20 +92,20 @@ var maxDepth = (maxDepth2, nestedValueName, message = `Error: You can only nest
|
|
|
91
92
|
var requiredIfSlugEq = (slug, slugKey = "slug", message = `This is a required field.`) => (value, context) => {
|
|
92
93
|
var _a, _b;
|
|
93
94
|
const slugs = typeof slug === "string" ? [slug] : slug;
|
|
94
|
-
const
|
|
95
|
-
if (!value && !!
|
|
96
|
-
return message.replace("{slugKey}", slugKey).replace("{
|
|
95
|
+
const slugValue = (_b = (_a = context.parent) == null ? void 0 : _a[slugKey]) == null ? void 0 : _b.current;
|
|
96
|
+
if (!value && !!slugValue && slugs.includes(slugValue)) {
|
|
97
|
+
return message.replace("{slugKey}", slugKey).replace("{operand}", slugs.join(", or ")).replace("{siblingSlugValue}", slugValue);
|
|
97
98
|
}
|
|
98
99
|
return true;
|
|
99
100
|
};
|
|
100
101
|
|
|
101
102
|
// src/requiredIfSiblingEq.ts
|
|
102
|
-
var requiredIfSiblingEq = (key,
|
|
103
|
-
var _a;
|
|
104
|
-
const
|
|
105
|
-
const
|
|
106
|
-
if (!value &&
|
|
107
|
-
return message.replace("{key}", key).replace("{
|
|
103
|
+
var requiredIfSiblingEq = (key, operand, message = "Required if {key} equals {operand}.") => (value, context) => {
|
|
104
|
+
var _a, _b;
|
|
105
|
+
const siblingValue = getSibling(key, context);
|
|
106
|
+
const operands = Array.isArray(operand) ? operand : [operand];
|
|
107
|
+
if (!value && operands.includes(siblingValue)) {
|
|
108
|
+
return message.replace("{key}", key).replace("{operand}", (_a = operands.join(", or ")) != null ? _a : "null").replace("{value}", (_b = operands.join(", or ")) != null ? _b : "null").replace("{siblingValue}", siblingValue);
|
|
108
109
|
}
|
|
109
110
|
return true;
|
|
110
111
|
};
|
|
@@ -116,6 +117,15 @@ var getSibling = (key, context) => {
|
|
|
116
117
|
const sibling = (0, import_lodash_es.get)(context.document, [...pathToParentObject, key]);
|
|
117
118
|
return sibling;
|
|
118
119
|
};
|
|
120
|
+
|
|
121
|
+
// src/regex.ts
|
|
122
|
+
var regex = (pattern, message = `\u201C{value}\u201D does not match the pattern {pattern}.`) => (value) => {
|
|
123
|
+
if (!value) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
const valueAsString = typeof value !== "string" ? value.toString() : value;
|
|
127
|
+
return pattern.test(valueAsString) ? true : message.replace("{value}", valueAsString).replace("{pattern}", pattern.toString());
|
|
128
|
+
};
|
|
119
129
|
// Annotate the CommonJS export names for ESM import in node:
|
|
120
130
|
0 && (module.exports = {
|
|
121
131
|
fileExtension,
|
|
@@ -123,6 +133,7 @@ var getSibling = (key, context) => {
|
|
|
123
133
|
maxDepth,
|
|
124
134
|
minDimensions,
|
|
125
135
|
referencedDocumentRequires,
|
|
136
|
+
regex,
|
|
126
137
|
requiredIfSiblingEq,
|
|
127
138
|
requiredIfSlugEq
|
|
128
139
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/referencedDocumentRequires.ts","../src/fileExtension.ts","../src/minDimensions.ts","../src/maxDepth.ts","../src/requiredIfSlugEq.ts","../src/requiredIfSiblingEq.ts","../src/lib/getSibling.ts"],"sourcesContent":["export * from \"./referencedDocumentRequires\"\nexport * from \"./fileExtension\"\nexport * from \"./minDimensions\"\nexport * from \"./maxDepth\"\nexport * from \"./requiredIfSlugEq\"\nexport * from \"./requiredIfSiblingEq\"\nexport * from \"./lib\"\n","import { ValidationContext } from \"sanity\"\n\nexport const referencedDocumentRequires = (\n documentType: string, \n field: string, \n message: string = `{documentType}’s {field} must be filled.`\n) => async (value: any | undefined, context: ValidationContext) => {\n if (!value?._ref) {\n return true\n }\n const client = context.getClient({ apiVersion: \"2022-08-12\" })\n // todo: use current API version, or test with no version at all\n\n // todo: if there's a value._type or value.referenced._type or something, we get rid of document.type from inputs\n const data = await client.fetch(`\n *[_type == \"${documentType}\" && _id == \"${value._ref}\"]{\n ${field}\n }[0]\n `) // TODO: why is typescript screaming about this? Fetch takes two parameters.\n if (!data[field]) {\n return message.replace(\"{documentType}\", documentType).replace(\"{field}\", field)\n }\n return true\n}\n","import { getExtension } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const fileExtension = (\n validFileExtension: string | Array<string>, \n message: string = `Image must be of type {validFileExtension}`\n) => (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const validExtensions = typeof validFileExtension === \"string\" ? [validFileExtension] : validFileExtension\n const filetype = getExtension(value.asset._ref)\n if (!validExtensions.includes(filetype)) {\n return message.replace(\"{validFileExtension}\", validExtensions.join(\", or \"))\n }\n return true\n}\n","import { getImageDimensions } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const minDimensions =\n ({ x, y }: { x: number; y: number }, message?: string) =>\n (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const { width, height } = getImageDimensions(value.asset._ref)\n if (!!x && width < x) {\n return message ? message.replace(\"{x}\", x.toString()).replace(\"{y}\", !y ? \"(any)\" : y.toString()) : `Image must be at least ${x} pixels wide.`\n }\n if (!!y && height < y) {\n return message ? message.replace(\"{x}\", !x ? \"(any)\" : x.toString()).replace(\"{y}\", y.toString()) : `Image must be at least ${y} pixels tall.`\n }\n return true\n }\n","import { ValidationContext } from \"sanity\"\n\nexport const maxDepth = (\n maxDepth: number, \n nestedValueName: string,\n message: string = `Error: You can only nest {nestedValueName} {maxDepth} levels deep.`\n) => (_: any, context: ValidationContext) => {\n let regex = new RegExp(String.raw`topLevelItems|${nestedValueName}`)\n const paths = (context.path as Array<any>).filter((e) => typeof e === \"string\" && e.match(regex))\n if (paths.length > maxDepth) {\n return message.replace(\"{nestedValueName}\", nestedValueName).replace(\"{maxDepth}\", maxDepth.toString())\n }\n return true\n}\n","import { ValidationContext } from \"sanity\"\n\n/*\nSanity has a funny idea of conditional fields. Every field is _always_ present, but it might be hidden.\nex. hidden: (node) => node.parent.slug === 'hideMe'\nThis works really well — unless a field marked as required gets hidden. \n\nThis validator conditionally marks a field as required only for specific slugs. It accepts a string or array of strings.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha'))\nvalidation: (rule) => rule.custom(requiredIfSlugEq(['alpha', 'beta']))\nvalidation: (rule) => rule.custom(requiredIfSlugNotEq(['beta']))\n```\n\nIf the key of your slug is not simply \"slug\", fill that in the optional second parameter.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha', 'id'))\n```\n\n\"Could this method be simpler if it just checked for the self.hidden state?\"\nNot possible, since the hidden state is not exposed to the context.\n\nBut even if it were, you wouldn't want to. There are valid reasons to make a component required but hidden.\nex. an admin- or developer-level identifier that you don't want civilians to see or edit.\n*/\n\nexport const requiredIfSlugEq = (\n slug: Array<string> | string, \n slugKey: string = \"slug\", \n message: string = `This is a required field.`\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const slugs = typeof slug === \"string\" ? [slug] : slug\n const currentSlugValue = (context.parent as any)?.[slugKey]?.current\n \n // todo: does slugKey exist? If not, fail.\n // todo: deal with nested slugKey (ex. metadata.slug)\n \n if (!value && !!currentSlugValue && slugs.includes(currentSlugValue)) {\n return message.replace(\"{slugKey}\", slugKey).replace(\"{slug}\", slugs.join(', or '))\n }\n return true\n }","import {getSibling} from './'\nimport {ValidationContext} from 'sanity'\n\n/*\nFor a given object that has multiple fields, mark a field as `required` if a sibling has a particular value.\n\n```\ndefineType({\n name: 'ifAlphaAlsoBeta',\n type: 'object',\n fields: [\n defineField({\n name: 'alpha',\n type: 'string',\n options: {\n list: ['left', 'right'],\n layout: 'radio',\n direction: 'horizontal',\n },\n }),\n defineField({\n name: 'beta',\n type: 'string',\n placeholder: 'If alpha is “left”, I’m also required',\n validation: (rule) => rule.custom(requiredIfSiblingEq('alpha', 'left')),\n })\n ],\n})\n```\n\nIncidentally, context.path is technically Array<sanity.PathSegment>.\n\nThat shouldn't matter, but dealing with that and remapping siblingKey as a PathSegment could be a possible future enhancement.\n*/\n\nexport const requiredIfSiblingEq = (\n key: string, \n comparison: string | number | null | Array<string | number | null>, \n message: string = 'Required if {key} equals {value}.'\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const sibling = getSibling(key, context)\n const comparisons = Array.isArray(comparison) ? comparison : [comparison]\n if (!value && comparisons.includes(sibling)) {\n return message.replace('{key}', key).replace('{value}', comparisons.join(', or ') ?? 'null')\n }\n return true\n }\n","import { get } from \"lodash-es\"\nimport { ValidationContext } from \"sanity\"\n\nexport const getSibling = (key: string | number, context: ValidationContext) => {\n const pathToParentObject = context.path!.slice(0, -1) as Array<string | number>\n const sibling = get(context.document, [...pathToParentObject, key])\n return sibling\n}\n\n/*\nTODO:\n There is an issue with finding a sibling when in an array element.\n If the context document looks something like this…\n {\n someArray: [\n {\n _key: 'abc123',\n targetSibling: 'herpderp'\n }\n ]\n }\n … we wind up with a path of…\n [ 'someArray', { _key: 'ab123' }, 'targetSibling' ]\n lodash.get() is trying to do an exact match, it doesn't know how to get object by _key.\n \n Will probably have to replace get() with a gnarly recursive lookup function.\n*/\n"],"mappings":"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","names":["import_asset_utils","maxDepth"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/referencedDocumentRequires.ts","../src/fileExtension.ts","../src/minDimensions.ts","../src/maxDepth.ts","../src/requiredIfSlugEq.ts","../src/requiredIfSiblingEq.ts","../src/lib/getSibling.ts","../src/regex.ts"],"sourcesContent":["export * from \"./referencedDocumentRequires\"\nexport * from \"./fileExtension\"\nexport * from \"./minDimensions\"\nexport * from \"./maxDepth\"\nexport * from \"./requiredIfSlugEq\"\nexport * from \"./requiredIfSiblingEq\"\nexport * from \"./lib\"\nexport * from \"./regex\"\n","import { ValidationContext } from \"sanity\"\n\nexport const referencedDocumentRequires = (\n documentType: string, \n field: string, \n message: string = `{documentType}’s {field} must be filled.`\n) => async (value: any | undefined, context: ValidationContext) => {\n if (!value?._ref) {\n return true\n }\n const client = context.getClient({ apiVersion: \"2022-08-12\" })\n // todo: use current API version, or test with no version at all\n\n // todo: if there's a value._type or value.referenced._type or something, we get rid of document.type from inputs\n const data = await client.fetch(`\n *[_type == \"${documentType}\" && _id == \"${value._ref}\"]{\n ${field}\n }[0]\n `) // TODO: why is typescript screaming about this? Fetch takes two parameters.\n if (!data[field]) {\n return message.replace(\"{documentType}\", documentType).replace(\"{field}\", field)\n }\n return true\n}\n","import { getExtension } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const fileExtension = (\n validFileExtension: string | Array<string>, \n message: string = `Image must be of type {validFileExtension}`\n) => (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const validExtensions = typeof validFileExtension === \"string\" ? [validFileExtension] : validFileExtension\n const filetype = getExtension(value.asset._ref)\n if (!validExtensions.includes(filetype)) {\n return message.replace(\"{validFileExtension}\", validExtensions.join(\", or \"))\n }\n return true\n}\n\n// todo: this should fail if its attached to a field that is not of type \"file\"","import { getImageDimensions } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const minDimensions =\n ({ x, y }: { x: number; y: number }, message?: string) =>\n (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const { width, height } = getImageDimensions(value.asset._ref)\n if (!!x && width < x) {\n return message \n ? message.replace(\"{width}\", width.toString())\n .replace(\"{height}\", height.toString())\n .replace(\"{x}\", x.toString())\n .replace(\"{y}\", !y ? \"(any)\" : y.toString()) \n : `Image must be at least ${x} pixels wide.`\n }\n if (!!y && height < y) {\n return message \n ? message.replace(\"{width}\", width.toString())\n .replace(\"{height}\", height.toString())\n .replace(\"{x}\", !x ? \"(any)\" : x.toString())\n .replace(\"{y}\", y.toString())\n : `Image must be at least ${y} pixels tall.`\n }\n return true\n }\n\n// todo: this should fail if its attached to a field that is not of type \"image\"","import { ValidationContext } from \"sanity\"\n\nexport const maxDepth = (\n maxDepth: number, \n key: string,\n message: string = `Error: You can only nest {key} {maxDepth} levels deep.`\n) => (_: any, context: ValidationContext) => {\n let regex = new RegExp(String.raw`topLevelItems|${key}`)\n const paths = (context.path as Array<any>).filter((e) => typeof e === \"string\" && e.match(regex))\n if (paths.length > maxDepth) {\n return message\n .replace(\"{key}\", key)\n .replace(\"{nestedValueName}\", key) // backward compatibility\n .replace(\"{maxDepth}\", maxDepth.toString())\n }\n return true\n}\n","import { ValidationContext } from \"sanity\"\n\n/*\nSanity has a funny idea of conditional fields. Every field is _always_ present, but it might be hidden.\nex. hidden: (node) => node.parent.slug === 'hideMe'\nThis works really well — unless a field marked as required gets hidden. \n\nThis validator conditionally marks a field as required only for specific slugs. It accepts a string or array of strings.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha'))\nvalidation: (rule) => rule.custom(requiredIfSlugEq(['alpha', 'beta']))\nvalidation: (rule) => rule.custom(requiredIfSlugNotEq(['beta']))\n```\n\nIf the key of your slug is not simply \"slug\", fill that in the optional second parameter.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha', 'id'))\n```\n\n\"Could this method be simpler if it just checked for the self.hidden state?\"\nNot possible, since the hidden state is not exposed to the context.\n\nBut even if it were, you wouldn't want to. There are valid reasons to make a component required but hidden.\nex. an admin- or developer-level identifier that you don't want civilians to see or edit.\n*/\n\nexport const requiredIfSlugEq = (\n slug: Array<string> | string, \n slugKey: string = \"slug\", \n message: string = `This is a required field.`\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const slugs = typeof slug === \"string\" ? [slug] : slug\n const slugValue = (context.parent as any)?.[slugKey]?.current\n \n // todo: does slugKey exist? If not, fail.\n // todo: deal with nested slugKey (ex. metadata.slug)\n \n if (!value && !!slugValue && slugs.includes(slugValue)) {\n return message\n .replace(\"{slugKey}\", slugKey)\n .replace(\"{operand}\", slugs.join(', or '))\n .replace(\"{siblingSlugValue}\", slugValue)\n }\n return true\n }","import {getSibling} from './'\nimport {ValidationContext} from 'sanity'\n\n/*\nFor a given object that has multiple fields, mark a field as `required` if a sibling has a particular value.\n\n```\ndefineType({\n name: 'ifAlphaAlsoBeta',\n type: 'object',\n fields: [\n defineField({\n name: 'alpha',\n type: 'string',\n options: {\n list: ['left', 'right'],\n layout: 'radio',\n direction: 'horizontal',\n },\n }),\n defineField({\n name: 'beta',\n type: 'string',\n placeholder: 'If alpha is “left”, I’m also required',\n validation: (rule) => rule.custom(requiredIfSiblingEq('alpha', 'left')),\n })\n ],\n})\n```\n\nIncidentally, context.path is technically Array<sanity.PathSegment>.\n\nThat shouldn't matter, but dealing with that and remapping siblingKey as a PathSegment could be a possible future enhancement.\n*/\n\nexport const requiredIfSiblingEq = (\n key: string, \n operand: string | number | null | Array<string | number | null>, \n message: string = 'Required if {key} equals {operand}.'\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const siblingValue = getSibling(key, context)\n const operands = Array.isArray(operand) ? operand : [operand]\n if (!value && operands.includes(siblingValue)) {\n return message\n .replace('{key}', key)\n .replace('{operand}', operands.join(', or ') ?? 'null')\n .replace('{value}', operands.join(', or ') ?? 'null') // backward compatibility\n .replace('{siblingValue}', siblingValue)\n }\n return true\n }\n","import { get } from \"lodash-es\"\nimport { ValidationContext } from \"sanity\"\n\nexport const getSibling = (key: string | number, context: ValidationContext) => {\n const pathToParentObject = context.path!.slice(0, -1) as Array<string | number>\n const sibling = get(context.document, [...pathToParentObject, key])\n return sibling\n}\n\n/*\nTODO:\n There is an issue with finding a sibling when in an array element.\n If the context document looks something like this…\n {\n someArray: [\n {\n _key: 'abc123',\n targetSibling: 'herpderp'\n }\n ]\n }\n … we wind up with a path of…\n [ 'someArray', { _key: 'ab123' }, 'targetSibling' ]\n lodash.get() is trying to do an exact match, it doesn't know how to get object by _key.\n \n Will probably have to replace get() with a gnarly recursive lookup function.\n*/\n","export const regex =\n (pattern: RegExp, message: string = `“{value}” does not match the pattern {pattern}.`) =>\n (value: unknown) => {\n if (!value) {\n return true\n }\n const valueAsString = typeof value !== \"string\" ? value.toString() : value\n return pattern.test(valueAsString) ? true : message.replace(\"{value}\", valueAsString).replace(\"{pattern}\", pattern.toString())\n }\n"],"mappings":"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","names":["import_asset_utils","maxDepth","regex"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -9,12 +9,14 @@ declare const minDimensions: ({ x, y }: {
|
|
|
9
9
|
y: number;
|
|
10
10
|
}, message?: string) => (value: FileValue | undefined) => string | true;
|
|
11
11
|
|
|
12
|
-
declare const maxDepth: (maxDepth: number,
|
|
12
|
+
declare const maxDepth: (maxDepth: number, key: string, message?: string) => (_: any, context: ValidationContext) => string | true;
|
|
13
13
|
|
|
14
14
|
declare const requiredIfSlugEq: (slug: Array<string> | string, slugKey?: string, message?: string) => (value: unknown | undefined, context: ValidationContext) => string | true;
|
|
15
15
|
|
|
16
|
-
declare const requiredIfSiblingEq: (key: string,
|
|
16
|
+
declare const requiredIfSiblingEq: (key: string, operand: string | number | null | Array<string | number | null>, message?: string) => (value: unknown | undefined, context: ValidationContext) => string | true;
|
|
17
17
|
|
|
18
18
|
declare const getSibling: (key: string | number, context: ValidationContext) => any;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
declare const regex: (pattern: RegExp, message?: string) => (value: unknown) => string | true;
|
|
21
|
+
|
|
22
|
+
export { fileExtension, getSibling, maxDepth, minDimensions, referencedDocumentRequires, regex, requiredIfSiblingEq, requiredIfSlugEq };
|
package/dist/index.d.ts
CHANGED
|
@@ -9,12 +9,14 @@ declare const minDimensions: ({ x, y }: {
|
|
|
9
9
|
y: number;
|
|
10
10
|
}, message?: string) => (value: FileValue | undefined) => string | true;
|
|
11
11
|
|
|
12
|
-
declare const maxDepth: (maxDepth: number,
|
|
12
|
+
declare const maxDepth: (maxDepth: number, key: string, message?: string) => (_: any, context: ValidationContext) => string | true;
|
|
13
13
|
|
|
14
14
|
declare const requiredIfSlugEq: (slug: Array<string> | string, slugKey?: string, message?: string) => (value: unknown | undefined, context: ValidationContext) => string | true;
|
|
15
15
|
|
|
16
|
-
declare const requiredIfSiblingEq: (key: string,
|
|
16
|
+
declare const requiredIfSiblingEq: (key: string, operand: string | number | null | Array<string | number | null>, message?: string) => (value: unknown | undefined, context: ValidationContext) => string | true;
|
|
17
17
|
|
|
18
18
|
declare const getSibling: (key: string | number, context: ValidationContext) => any;
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
declare const regex: (pattern: RegExp, message?: string) => (value: unknown) => string | true;
|
|
21
|
+
|
|
22
|
+
export { fileExtension, getSibling, maxDepth, minDimensions, referencedDocumentRequires, regex, requiredIfSiblingEq, requiredIfSlugEq };
|
package/dist/index.js
CHANGED
|
@@ -37,20 +37,20 @@ var minDimensions = ({ x, y }, message) => (value) => {
|
|
|
37
37
|
}
|
|
38
38
|
const { width, height } = getImageDimensions(value.asset._ref);
|
|
39
39
|
if (!!x && width < x) {
|
|
40
|
-
return message ? message.replace("{x}", x.toString()).replace("{y}", !y ? "(any)" : y.toString()) : `Image must be at least ${x} pixels wide.`;
|
|
40
|
+
return message ? message.replace("{width}", width.toString()).replace("{height}", height.toString()).replace("{x}", x.toString()).replace("{y}", !y ? "(any)" : y.toString()) : `Image must be at least ${x} pixels wide.`;
|
|
41
41
|
}
|
|
42
42
|
if (!!y && height < y) {
|
|
43
|
-
return message ? message.replace("{x}", !x ? "(any)" : x.toString()).replace("{y}", y.toString()) : `Image must be at least ${y} pixels tall.`;
|
|
43
|
+
return message ? message.replace("{width}", width.toString()).replace("{height}", height.toString()).replace("{x}", !x ? "(any)" : x.toString()).replace("{y}", y.toString()) : `Image must be at least ${y} pixels tall.`;
|
|
44
44
|
}
|
|
45
45
|
return true;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
48
|
// src/maxDepth.ts
|
|
49
|
-
var maxDepth = (maxDepth2,
|
|
50
|
-
let
|
|
51
|
-
const paths = context.path.filter((e) => typeof e === "string" && e.match(
|
|
49
|
+
var maxDepth = (maxDepth2, key, message = `Error: You can only nest {key} {maxDepth} levels deep.`) => (_, context) => {
|
|
50
|
+
let regex2 = new RegExp(String.raw`topLevelItems|${key}`);
|
|
51
|
+
const paths = context.path.filter((e) => typeof e === "string" && e.match(regex2));
|
|
52
52
|
if (paths.length > maxDepth2) {
|
|
53
|
-
return message.replace("{nestedValueName}",
|
|
53
|
+
return message.replace("{key}", key).replace("{nestedValueName}", key).replace("{maxDepth}", maxDepth2.toString());
|
|
54
54
|
}
|
|
55
55
|
return true;
|
|
56
56
|
};
|
|
@@ -59,20 +59,20 @@ var maxDepth = (maxDepth2, nestedValueName, message = `Error: You can only nest
|
|
|
59
59
|
var requiredIfSlugEq = (slug, slugKey = "slug", message = `This is a required field.`) => (value, context) => {
|
|
60
60
|
var _a, _b;
|
|
61
61
|
const slugs = typeof slug === "string" ? [slug] : slug;
|
|
62
|
-
const
|
|
63
|
-
if (!value && !!
|
|
64
|
-
return message.replace("{slugKey}", slugKey).replace("{
|
|
62
|
+
const slugValue = (_b = (_a = context.parent) == null ? void 0 : _a[slugKey]) == null ? void 0 : _b.current;
|
|
63
|
+
if (!value && !!slugValue && slugs.includes(slugValue)) {
|
|
64
|
+
return message.replace("{slugKey}", slugKey).replace("{operand}", slugs.join(", or ")).replace("{siblingSlugValue}", slugValue);
|
|
65
65
|
}
|
|
66
66
|
return true;
|
|
67
67
|
};
|
|
68
68
|
|
|
69
69
|
// src/requiredIfSiblingEq.ts
|
|
70
|
-
var requiredIfSiblingEq = (key,
|
|
71
|
-
var _a;
|
|
72
|
-
const
|
|
73
|
-
const
|
|
74
|
-
if (!value &&
|
|
75
|
-
return message.replace("{key}", key).replace("{
|
|
70
|
+
var requiredIfSiblingEq = (key, operand, message = "Required if {key} equals {operand}.") => (value, context) => {
|
|
71
|
+
var _a, _b;
|
|
72
|
+
const siblingValue = getSibling(key, context);
|
|
73
|
+
const operands = Array.isArray(operand) ? operand : [operand];
|
|
74
|
+
if (!value && operands.includes(siblingValue)) {
|
|
75
|
+
return message.replace("{key}", key).replace("{operand}", (_a = operands.join(", or ")) != null ? _a : "null").replace("{value}", (_b = operands.join(", or ")) != null ? _b : "null").replace("{siblingValue}", siblingValue);
|
|
76
76
|
}
|
|
77
77
|
return true;
|
|
78
78
|
};
|
|
@@ -84,12 +84,22 @@ var getSibling = (key, context) => {
|
|
|
84
84
|
const sibling = get(context.document, [...pathToParentObject, key]);
|
|
85
85
|
return sibling;
|
|
86
86
|
};
|
|
87
|
+
|
|
88
|
+
// src/regex.ts
|
|
89
|
+
var regex = (pattern, message = `\u201C{value}\u201D does not match the pattern {pattern}.`) => (value) => {
|
|
90
|
+
if (!value) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
const valueAsString = typeof value !== "string" ? value.toString() : value;
|
|
94
|
+
return pattern.test(valueAsString) ? true : message.replace("{value}", valueAsString).replace("{pattern}", pattern.toString());
|
|
95
|
+
};
|
|
87
96
|
export {
|
|
88
97
|
fileExtension,
|
|
89
98
|
getSibling,
|
|
90
99
|
maxDepth,
|
|
91
100
|
minDimensions,
|
|
92
101
|
referencedDocumentRequires,
|
|
102
|
+
regex,
|
|
93
103
|
requiredIfSiblingEq,
|
|
94
104
|
requiredIfSlugEq
|
|
95
105
|
};
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/referencedDocumentRequires.ts","../src/fileExtension.ts","../src/minDimensions.ts","../src/maxDepth.ts","../src/requiredIfSlugEq.ts","../src/requiredIfSiblingEq.ts","../src/lib/getSibling.ts"],"sourcesContent":["import { ValidationContext } from \"sanity\"\n\nexport const referencedDocumentRequires = (\n documentType: string, \n field: string, \n message: string = `{documentType}’s {field} must be filled.`\n) => async (value: any | undefined, context: ValidationContext) => {\n if (!value?._ref) {\n return true\n }\n const client = context.getClient({ apiVersion: \"2022-08-12\" })\n // todo: use current API version, or test with no version at all\n\n // todo: if there's a value._type or value.referenced._type or something, we get rid of document.type from inputs\n const data = await client.fetch(`\n *[_type == \"${documentType}\" && _id == \"${value._ref}\"]{\n ${field}\n }[0]\n `) // TODO: why is typescript screaming about this? Fetch takes two parameters.\n if (!data[field]) {\n return message.replace(\"{documentType}\", documentType).replace(\"{field}\", field)\n }\n return true\n}\n","import { getExtension } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const fileExtension = (\n validFileExtension: string | Array<string>, \n message: string = `Image must be of type {validFileExtension}`\n) => (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const validExtensions = typeof validFileExtension === \"string\" ? [validFileExtension] : validFileExtension\n const filetype = getExtension(value.asset._ref)\n if (!validExtensions.includes(filetype)) {\n return message.replace(\"{validFileExtension}\", validExtensions.join(\", or \"))\n }\n return true\n}\n","import { getImageDimensions } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const minDimensions =\n ({ x, y }: { x: number; y: number }, message?: string) =>\n (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const { width, height } = getImageDimensions(value.asset._ref)\n if (!!x && width < x) {\n return message ? message.replace(\"{x}\", x.toString()).replace(\"{y}\", !y ? \"(any)\" : y.toString()) : `Image must be at least ${x} pixels wide.`\n }\n if (!!y && height < y) {\n return message ? message.replace(\"{x}\", !x ? \"(any)\" : x.toString()).replace(\"{y}\", y.toString()) : `Image must be at least ${y} pixels tall.`\n }\n return true\n }\n","import { ValidationContext } from \"sanity\"\n\nexport const maxDepth = (\n maxDepth: number, \n nestedValueName: string,\n message: string = `Error: You can only nest {nestedValueName} {maxDepth} levels deep.`\n) => (_: any, context: ValidationContext) => {\n let regex = new RegExp(String.raw`topLevelItems|${nestedValueName}`)\n const paths = (context.path as Array<any>).filter((e) => typeof e === \"string\" && e.match(regex))\n if (paths.length > maxDepth) {\n return message.replace(\"{nestedValueName}\", nestedValueName).replace(\"{maxDepth}\", maxDepth.toString())\n }\n return true\n}\n","import { ValidationContext } from \"sanity\"\n\n/*\nSanity has a funny idea of conditional fields. Every field is _always_ present, but it might be hidden.\nex. hidden: (node) => node.parent.slug === 'hideMe'\nThis works really well — unless a field marked as required gets hidden. \n\nThis validator conditionally marks a field as required only for specific slugs. It accepts a string or array of strings.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha'))\nvalidation: (rule) => rule.custom(requiredIfSlugEq(['alpha', 'beta']))\nvalidation: (rule) => rule.custom(requiredIfSlugNotEq(['beta']))\n```\n\nIf the key of your slug is not simply \"slug\", fill that in the optional second parameter.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha', 'id'))\n```\n\n\"Could this method be simpler if it just checked for the self.hidden state?\"\nNot possible, since the hidden state is not exposed to the context.\n\nBut even if it were, you wouldn't want to. There are valid reasons to make a component required but hidden.\nex. an admin- or developer-level identifier that you don't want civilians to see or edit.\n*/\n\nexport const requiredIfSlugEq = (\n slug: Array<string> | string, \n slugKey: string = \"slug\", \n message: string = `This is a required field.`\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const slugs = typeof slug === \"string\" ? [slug] : slug\n const currentSlugValue = (context.parent as any)?.[slugKey]?.current\n \n // todo: does slugKey exist? If not, fail.\n // todo: deal with nested slugKey (ex. metadata.slug)\n \n if (!value && !!currentSlugValue && slugs.includes(currentSlugValue)) {\n return message.replace(\"{slugKey}\", slugKey).replace(\"{slug}\", slugs.join(', or '))\n }\n return true\n }","import {getSibling} from './'\nimport {ValidationContext} from 'sanity'\n\n/*\nFor a given object that has multiple fields, mark a field as `required` if a sibling has a particular value.\n\n```\ndefineType({\n name: 'ifAlphaAlsoBeta',\n type: 'object',\n fields: [\n defineField({\n name: 'alpha',\n type: 'string',\n options: {\n list: ['left', 'right'],\n layout: 'radio',\n direction: 'horizontal',\n },\n }),\n defineField({\n name: 'beta',\n type: 'string',\n placeholder: 'If alpha is “left”, I’m also required',\n validation: (rule) => rule.custom(requiredIfSiblingEq('alpha', 'left')),\n })\n ],\n})\n```\n\nIncidentally, context.path is technically Array<sanity.PathSegment>.\n\nThat shouldn't matter, but dealing with that and remapping siblingKey as a PathSegment could be a possible future enhancement.\n*/\n\nexport const requiredIfSiblingEq = (\n key: string, \n comparison: string | number | null | Array<string | number | null>, \n message: string = 'Required if {key} equals {value}.'\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const sibling = getSibling(key, context)\n const comparisons = Array.isArray(comparison) ? comparison : [comparison]\n if (!value && comparisons.includes(sibling)) {\n return message.replace('{key}', key).replace('{value}', comparisons.join(', or ') ?? 'null')\n }\n return true\n }\n","import { get } from \"lodash-es\"\nimport { ValidationContext } from \"sanity\"\n\nexport const getSibling = (key: string | number, context: ValidationContext) => {\n const pathToParentObject = context.path!.slice(0, -1) as Array<string | number>\n const sibling = get(context.document, [...pathToParentObject, key])\n return sibling\n}\n\n/*\nTODO:\n There is an issue with finding a sibling when in an array element.\n If the context document looks something like this…\n {\n someArray: [\n {\n _key: 'abc123',\n targetSibling: 'herpderp'\n }\n ]\n }\n … we wind up with a path of…\n [ 'someArray', { _key: 'ab123' }, 'targetSibling' ]\n lodash.get() is trying to do an exact match, it doesn't know how to get object by _key.\n \n Will probably have to replace get() with a gnarly recursive lookup function.\n*/\n"],"mappings":"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","names":["maxDepth"]}
|
|
1
|
+
{"version":3,"sources":["../src/referencedDocumentRequires.ts","../src/fileExtension.ts","../src/minDimensions.ts","../src/maxDepth.ts","../src/requiredIfSlugEq.ts","../src/requiredIfSiblingEq.ts","../src/lib/getSibling.ts","../src/regex.ts"],"sourcesContent":["import { ValidationContext } from \"sanity\"\n\nexport const referencedDocumentRequires = (\n documentType: string, \n field: string, \n message: string = `{documentType}’s {field} must be filled.`\n) => async (value: any | undefined, context: ValidationContext) => {\n if (!value?._ref) {\n return true\n }\n const client = context.getClient({ apiVersion: \"2022-08-12\" })\n // todo: use current API version, or test with no version at all\n\n // todo: if there's a value._type or value.referenced._type or something, we get rid of document.type from inputs\n const data = await client.fetch(`\n *[_type == \"${documentType}\" && _id == \"${value._ref}\"]{\n ${field}\n }[0]\n `) // TODO: why is typescript screaming about this? Fetch takes two parameters.\n if (!data[field]) {\n return message.replace(\"{documentType}\", documentType).replace(\"{field}\", field)\n }\n return true\n}\n","import { getExtension } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const fileExtension = (\n validFileExtension: string | Array<string>, \n message: string = `Image must be of type {validFileExtension}`\n) => (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const validExtensions = typeof validFileExtension === \"string\" ? [validFileExtension] : validFileExtension\n const filetype = getExtension(value.asset._ref)\n if (!validExtensions.includes(filetype)) {\n return message.replace(\"{validFileExtension}\", validExtensions.join(\", or \"))\n }\n return true\n}\n\n// todo: this should fail if its attached to a field that is not of type \"file\"","import { getImageDimensions } from \"@sanity/asset-utils\"\nimport { FileValue } from \"sanity\"\n\nexport const minDimensions =\n ({ x, y }: { x: number; y: number }, message?: string) =>\n (value: FileValue | undefined) => {\n if (!value || !value.asset) {\n return true\n }\n const { width, height } = getImageDimensions(value.asset._ref)\n if (!!x && width < x) {\n return message \n ? message.replace(\"{width}\", width.toString())\n .replace(\"{height}\", height.toString())\n .replace(\"{x}\", x.toString())\n .replace(\"{y}\", !y ? \"(any)\" : y.toString()) \n : `Image must be at least ${x} pixels wide.`\n }\n if (!!y && height < y) {\n return message \n ? message.replace(\"{width}\", width.toString())\n .replace(\"{height}\", height.toString())\n .replace(\"{x}\", !x ? \"(any)\" : x.toString())\n .replace(\"{y}\", y.toString())\n : `Image must be at least ${y} pixels tall.`\n }\n return true\n }\n\n// todo: this should fail if its attached to a field that is not of type \"image\"","import { ValidationContext } from \"sanity\"\n\nexport const maxDepth = (\n maxDepth: number, \n key: string,\n message: string = `Error: You can only nest {key} {maxDepth} levels deep.`\n) => (_: any, context: ValidationContext) => {\n let regex = new RegExp(String.raw`topLevelItems|${key}`)\n const paths = (context.path as Array<any>).filter((e) => typeof e === \"string\" && e.match(regex))\n if (paths.length > maxDepth) {\n return message\n .replace(\"{key}\", key)\n .replace(\"{nestedValueName}\", key) // backward compatibility\n .replace(\"{maxDepth}\", maxDepth.toString())\n }\n return true\n}\n","import { ValidationContext } from \"sanity\"\n\n/*\nSanity has a funny idea of conditional fields. Every field is _always_ present, but it might be hidden.\nex. hidden: (node) => node.parent.slug === 'hideMe'\nThis works really well — unless a field marked as required gets hidden. \n\nThis validator conditionally marks a field as required only for specific slugs. It accepts a string or array of strings.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha'))\nvalidation: (rule) => rule.custom(requiredIfSlugEq(['alpha', 'beta']))\nvalidation: (rule) => rule.custom(requiredIfSlugNotEq(['beta']))\n```\n\nIf the key of your slug is not simply \"slug\", fill that in the optional second parameter.\n```\nvalidation: (rule) => rule.custom(requiredIfSlugEq('alpha', 'id'))\n```\n\n\"Could this method be simpler if it just checked for the self.hidden state?\"\nNot possible, since the hidden state is not exposed to the context.\n\nBut even if it were, you wouldn't want to. There are valid reasons to make a component required but hidden.\nex. an admin- or developer-level identifier that you don't want civilians to see or edit.\n*/\n\nexport const requiredIfSlugEq = (\n slug: Array<string> | string, \n slugKey: string = \"slug\", \n message: string = `This is a required field.`\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const slugs = typeof slug === \"string\" ? [slug] : slug\n const slugValue = (context.parent as any)?.[slugKey]?.current\n \n // todo: does slugKey exist? If not, fail.\n // todo: deal with nested slugKey (ex. metadata.slug)\n \n if (!value && !!slugValue && slugs.includes(slugValue)) {\n return message\n .replace(\"{slugKey}\", slugKey)\n .replace(\"{operand}\", slugs.join(', or '))\n .replace(\"{siblingSlugValue}\", slugValue)\n }\n return true\n }","import {getSibling} from './'\nimport {ValidationContext} from 'sanity'\n\n/*\nFor a given object that has multiple fields, mark a field as `required` if a sibling has a particular value.\n\n```\ndefineType({\n name: 'ifAlphaAlsoBeta',\n type: 'object',\n fields: [\n defineField({\n name: 'alpha',\n type: 'string',\n options: {\n list: ['left', 'right'],\n layout: 'radio',\n direction: 'horizontal',\n },\n }),\n defineField({\n name: 'beta',\n type: 'string',\n placeholder: 'If alpha is “left”, I’m also required',\n validation: (rule) => rule.custom(requiredIfSiblingEq('alpha', 'left')),\n })\n ],\n})\n```\n\nIncidentally, context.path is technically Array<sanity.PathSegment>.\n\nThat shouldn't matter, but dealing with that and remapping siblingKey as a PathSegment could be a possible future enhancement.\n*/\n\nexport const requiredIfSiblingEq = (\n key: string, \n operand: string | number | null | Array<string | number | null>, \n message: string = 'Required if {key} equals {operand}.'\n) =>\n (value: unknown | undefined, context: ValidationContext) => {\n const siblingValue = getSibling(key, context)\n const operands = Array.isArray(operand) ? operand : [operand]\n if (!value && operands.includes(siblingValue)) {\n return message\n .replace('{key}', key)\n .replace('{operand}', operands.join(', or ') ?? 'null')\n .replace('{value}', operands.join(', or ') ?? 'null') // backward compatibility\n .replace('{siblingValue}', siblingValue)\n }\n return true\n }\n","import { get } from \"lodash-es\"\nimport { ValidationContext } from \"sanity\"\n\nexport const getSibling = (key: string | number, context: ValidationContext) => {\n const pathToParentObject = context.path!.slice(0, -1) as Array<string | number>\n const sibling = get(context.document, [...pathToParentObject, key])\n return sibling\n}\n\n/*\nTODO:\n There is an issue with finding a sibling when in an array element.\n If the context document looks something like this…\n {\n someArray: [\n {\n _key: 'abc123',\n targetSibling: 'herpderp'\n }\n ]\n }\n … we wind up with a path of…\n [ 'someArray', { _key: 'ab123' }, 'targetSibling' ]\n lodash.get() is trying to do an exact match, it doesn't know how to get object by _key.\n \n Will probably have to replace get() with a gnarly recursive lookup function.\n*/\n","export const regex =\n (pattern: RegExp, message: string = `“{value}” does not match the pattern {pattern}.`) =>\n (value: unknown) => {\n if (!value) {\n return true\n }\n const valueAsString = typeof value !== \"string\" ? value.toString() : value\n return pattern.test(valueAsString) ? true : message.replace(\"{value}\", valueAsString).replace(\"{pattern}\", pattern.toString())\n }\n"],"mappings":"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","names":["maxDepth","regex"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"package-name": "sanity-advanced-validators",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Advanced input validation tools for Sanity CMS.",
|
|
5
5
|
"author": "Eric_WVGG",
|
|
6
6
|
"license": "MIT",
|
|
@@ -22,13 +22,13 @@
|
|
|
22
22
|
],
|
|
23
23
|
"dependencies": {
|
|
24
24
|
"@sanity/asset-utils": "^2.2.1",
|
|
25
|
-
"lodash-es": "^4.
|
|
26
|
-
"sanity": "^
|
|
25
|
+
"lodash-es": "^4.17.0",
|
|
26
|
+
"sanity": "^3.9.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@types/lodash-es": "^4.
|
|
29
|
+
"@types/lodash-es": "^4.17.0",
|
|
30
30
|
"tsup": "^8.5.0",
|
|
31
|
-
"typescript": "^5.
|
|
31
|
+
"typescript": "^5.8.0",
|
|
32
32
|
"vitest": "^3.2.4"
|
|
33
33
|
},
|
|
34
34
|
"name": "sanity-advanced-validators",
|