@valbuild/core 0.126.0 → 0.128.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/CHANGELOG.md +296 -0
- package/dist/declarations/src/index.d.ts +5 -1
- package/dist/declarations/src/initSchema.d.ts +31 -15
- package/dist/declarations/src/locale.d.ts +55 -0
- package/dist/declarations/src/schema/array.d.ts +4 -0
- package/dist/declarations/src/schema/declaredKeys.d.ts +37 -0
- package/dist/declarations/src/schema/discriminatedUnion.d.ts +4 -0
- package/dist/declarations/src/schema/discriminatedUnionBranch.d.ts +20 -0
- package/dist/declarations/src/schema/enum.d.ts +9 -0
- package/dist/declarations/src/schema/file.d.ts +3 -3
- package/dist/declarations/src/schema/{files.d.ts → fileset.d.ts} +10 -10
- package/dist/declarations/src/schema/hasRemoteFileSchema.d.ts +3 -3
- package/dist/declarations/src/schema/image.d.ts +9 -5
- package/dist/declarations/src/schema/{images.d.ts → imageset.d.ts} +54 -21
- package/dist/declarations/src/schema/index.d.ts +116 -3
- package/dist/declarations/src/schema/literal.d.ts +2 -0
- package/dist/declarations/src/schema/locale.d.ts +132 -0
- package/dist/declarations/src/schema/object.d.ts +7 -0
- package/dist/declarations/src/schema/record.d.ts +57 -7
- package/dist/declarations/src/schema/settings.d.ts +54 -1
- package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
- package/dist/declarations/src/source/settings.d.ts +51 -0
- package/dist/{index-6db78f3c.cjs.prod.js → index-053ccde2.cjs.prod.js} +1114 -91
- package/dist/{index-b41b638f.cjs.dev.js → index-c3ae637d.cjs.dev.js} +1114 -91
- package/dist/{index-cd25c2dc.esm.js → index-db41de3c.esm.js} +1109 -92
- package/dist/valbuild-core.cjs.dev.js +7 -1
- package/dist/valbuild-core.cjs.prod.js +7 -1
- package/dist/valbuild-core.esm.js +1 -1
- package/package.json +1 -1
- package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
- package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
- package/patch/dist/valbuild-core-patch.esm.js +2 -2
|
@@ -4,9 +4,9 @@ import { RecordSchema } from "./record.js";
|
|
|
4
4
|
import { ObjectSchema } from "./object.js";
|
|
5
5
|
import { StringSchema } from "./string.js";
|
|
6
6
|
/**
|
|
7
|
-
* Options for s.
|
|
7
|
+
* Options for s.fileset()
|
|
8
8
|
*/
|
|
9
|
-
export type
|
|
9
|
+
export type FilesetOptions = {
|
|
10
10
|
/**
|
|
11
11
|
* The accepted mime type pattern (e.g., "application/pdf", "text/*", "*\/*")
|
|
12
12
|
*/
|
|
@@ -20,19 +20,19 @@ export type FilesOptions = {
|
|
|
20
20
|
* collection that had simply not said where it wanted its files silently shared
|
|
21
21
|
* a directory with every other one.
|
|
22
22
|
*/
|
|
23
|
-
|
|
23
|
+
dir: "/public" | `/public/${string}`;
|
|
24
24
|
};
|
|
25
25
|
/**
|
|
26
26
|
* Metadata for a file entry in the files record
|
|
27
27
|
*/
|
|
28
|
-
export type
|
|
28
|
+
export type FilesetEntryMetadata = {
|
|
29
29
|
mimeType: string;
|
|
30
30
|
};
|
|
31
|
-
export type
|
|
32
|
-
type
|
|
31
|
+
export type SerializedFilesetSchema = SerializedRecordSchema;
|
|
32
|
+
type FilesetItemProps = {
|
|
33
33
|
mimeType: StringSchema<string>;
|
|
34
34
|
};
|
|
35
|
-
type
|
|
35
|
+
type FilesetItemSrc = {
|
|
36
36
|
mimeType: string;
|
|
37
37
|
};
|
|
38
38
|
/**
|
|
@@ -42,9 +42,9 @@ type FilesItemSrc = {
|
|
|
42
42
|
*
|
|
43
43
|
* @example
|
|
44
44
|
* ```typescript
|
|
45
|
-
* const schema = s.
|
|
45
|
+
* const schema = s.fileset({
|
|
46
46
|
* accept: "application/pdf",
|
|
47
|
-
*
|
|
47
|
+
* dir: "/public/val/documents",
|
|
48
48
|
* });
|
|
49
49
|
* export default c.define("/content/documents.val.ts", schema, {
|
|
50
50
|
* "/public/val/documents/report.pdf": {
|
|
@@ -53,5 +53,5 @@ type FilesItemSrc = {
|
|
|
53
53
|
* });
|
|
54
54
|
* ```
|
|
55
55
|
*/
|
|
56
|
-
export declare const
|
|
56
|
+
export declare const fileset: (options: FilesetOptions) => RecordSchema<ObjectSchema<FilesetItemProps, FilesetItemSrc>, Schema<string>, Record<string, FilesetEntryMetadata>>;
|
|
57
57
|
export {};
|
|
@@ -5,7 +5,7 @@ import type { SerializedSchema } from "./index.js";
|
|
|
5
5
|
* The one answer to that question. There used to be two — `hasRemoteFileSchema`
|
|
6
6
|
* in the server, gating whether `/save` demands remote credentials, and
|
|
7
7
|
* `findRequiredRemoteFiles` in the Studio, gating the `/remote/settings` fetch —
|
|
8
|
-
* and they disagreed about `s.
|
|
8
|
+
* and they disagreed about `s.imageset({ ... }).remote()`: the Studio's version
|
|
9
9
|
* counted a remote media record, the server's did not, because a media
|
|
10
10
|
* collection serializes as a `record` of metadata rather than as an image
|
|
11
11
|
* schema.
|
|
@@ -43,8 +43,8 @@ export declare function hasRemoteFileSchema(schema: SerializedSchema): boolean;
|
|
|
43
43
|
*
|
|
44
44
|
* It is a walk over the SERIALIZED schema for the reasons given above, and for
|
|
45
45
|
* one more that is specific to this question: the two cases most likely to be
|
|
46
|
-
* missed are invisible to a value type. A media collection (`s.
|
|
47
|
-
* `s.
|
|
46
|
+
* missed are invisible to a value type. A media collection (`s.imageset()` /
|
|
47
|
+
* `s.fileset()`) serializes as a record of metadata with no image schema inside
|
|
48
48
|
* it, and a richtext inline image lives in a constructor argument. A check
|
|
49
49
|
* written against the item TYPE would silently pass both — and the gallery case
|
|
50
50
|
* already has a data-loss incident attached to it (see above).
|
|
@@ -5,7 +5,7 @@ import { ValidationError, ValidationErrors } from "./validation/ValidationError.
|
|
|
5
5
|
import { ValModule } from "../index.js";
|
|
6
6
|
import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
|
|
7
7
|
import { FieldRender } from "../render.js";
|
|
8
|
-
import {
|
|
8
|
+
import { AltSource, ImagesetEntryMetadata } from "./imageset.js";
|
|
9
9
|
/**
|
|
10
10
|
* How an uploaded image is re-encoded in the browser, before it is uploaded.
|
|
11
11
|
*
|
|
@@ -41,7 +41,7 @@ export type GalleryImageOptions = {
|
|
|
41
41
|
encode?: ImageEncodeOption;
|
|
42
42
|
};
|
|
43
43
|
export type ImageOptions = {
|
|
44
|
-
|
|
44
|
+
dir?: string;
|
|
45
45
|
accept?: string;
|
|
46
46
|
encode?: ImageEncodeOption;
|
|
47
47
|
};
|
|
@@ -81,7 +81,7 @@ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<
|
|
|
81
81
|
private readonly description?;
|
|
82
82
|
private readonly renderInput;
|
|
83
83
|
private readonly previewInput;
|
|
84
|
-
constructor(options?: ImageOptions | undefined, opt?: boolean, isRemote?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], moduleMetadata?: Record<ModulePath, Record<string,
|
|
84
|
+
constructor(options?: ImageOptions | undefined, opt?: boolean, isRemote?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], moduleMetadata?: Record<ModulePath, Record<string, ImagesetEntryMetadata<AltSource>>>, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
|
|
85
85
|
/**
|
|
86
86
|
* Describe this field.
|
|
87
87
|
*
|
|
@@ -119,7 +119,7 @@ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<
|
|
|
119
119
|
*
|
|
120
120
|
* @example
|
|
121
121
|
* const schema = s
|
|
122
|
-
* .image({ accept: "image/webp",
|
|
122
|
+
* .image({ accept: "image/webp", dir: "/public/val/images" })
|
|
123
123
|
* .remote();
|
|
124
124
|
* export default c.define("/example.val.ts", schema, {
|
|
125
125
|
* path: "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/example.webp",
|
|
@@ -163,6 +163,10 @@ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<
|
|
|
163
163
|
/**
|
|
164
164
|
* The entries of the gallery this field points at, or null when it is a
|
|
165
165
|
* standalone field.
|
|
166
|
+
*
|
|
167
|
+
* `AltSource` rather than a specific alt type: a field never reads an entry's
|
|
168
|
+
* alt — it carries its own — so it only needs the entry keys and the
|
|
169
|
+
* dimensions, and a gallery of any alt shape can back it.
|
|
166
170
|
*/
|
|
167
171
|
private galleryEntries;
|
|
168
172
|
protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
|
|
@@ -221,6 +225,6 @@ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<
|
|
|
221
225
|
* An image picked from a gallery. Its dimensions and mime type live in the
|
|
222
226
|
* gallery, so the field carries only what a person typed.
|
|
223
227
|
*/
|
|
224
|
-
export declare function image(galleryModule: ValModule<Record<string,
|
|
228
|
+
export declare function image(galleryModule: ValModule<Record<string, ImagesetEntryMetadata<AltSource>>>, galleryOptions?: GalleryImageOptions): ImageSchema<GalleryImageSource>;
|
|
225
229
|
/** An image of its own, carrying its own dimensions and mime type. */
|
|
226
230
|
export declare function image(options?: ImageOptions): ImageSchema<ImageSource>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Schema } from "./index.js";
|
|
2
|
+
import type { SelectorOfSchema } from "./index.js";
|
|
2
3
|
import type { SerializedRecordSchema } from "./record.js";
|
|
3
4
|
import { RecordSchema } from "./record.js";
|
|
4
5
|
import { ObjectSchema } from "./object.js";
|
|
@@ -10,9 +11,43 @@ import type { ImageEncodeOption } from "./image.js";
|
|
|
10
11
|
*/
|
|
11
12
|
export type AltSchema = StringSchema<string> | StringSchema<string | null> | RecordSchema<StringSchema<string>, Schema<string>, Record<string, string>>;
|
|
12
13
|
/**
|
|
13
|
-
*
|
|
14
|
+
* What an entry's `alt` holds, for a given `alt` schema.
|
|
15
|
+
*
|
|
16
|
+
* This is the schema's own source type, not a second list that has to be kept
|
|
17
|
+
* in step with `AltSchema`: `s.string()` gives `string`, `s.string().nullable()`
|
|
18
|
+
* gives `string | null`, and `s.record(s.string())` gives
|
|
19
|
+
* `Record<string, string>`.
|
|
20
|
+
*/
|
|
21
|
+
export type AltSourceOf<Alt extends AltSchema> = SelectorOfSchema<Alt>;
|
|
22
|
+
/** Every `alt` value any `AltSchema` can produce. */
|
|
23
|
+
export type AltSource = AltSourceOf<AltSchema>;
|
|
24
|
+
/**
|
|
25
|
+
* Options for s.imageset()
|
|
26
|
+
*/
|
|
27
|
+
export type ImagesetOptions<Accept extends `image/${string}`, Alt extends AltSchema = StringSchema<string | null>> = ImagesetOptionsBase<Accept> & ImagesetAltOption<Alt>;
|
|
28
|
+
/**
|
|
29
|
+
* `alt` is optional only when it is the default schema.
|
|
30
|
+
*
|
|
31
|
+
* Naming a different `Alt` and then leaving `alt` out would make the fallback
|
|
32
|
+
* in `imageset()` a lie — `Alt` would say `s.string()` while the value was a
|
|
33
|
+
* nullable one. Requiring it in that case is what makes the fallback sound:
|
|
34
|
+
* the branch that uses it is reachable only when `Alt` IS the default.
|
|
14
35
|
*/
|
|
15
|
-
|
|
36
|
+
type ImagesetAltOption<Alt extends AltSchema> = [
|
|
37
|
+
StringSchema<string | null>
|
|
38
|
+
] extends [Alt] ? {
|
|
39
|
+
/**
|
|
40
|
+
* Alt text schema. Can be:
|
|
41
|
+
* - s.string() for required alt text
|
|
42
|
+
* - s.string().nullable() for optional alt text (default)
|
|
43
|
+
* - s.record(s.string(), s.string()) for locale-based alt text
|
|
44
|
+
*/
|
|
45
|
+
alt?: Alt;
|
|
46
|
+
} : {
|
|
47
|
+
/** Alt text schema — required, because it is not the default one. */
|
|
48
|
+
alt: Alt;
|
|
49
|
+
};
|
|
50
|
+
type ImagesetOptionsBase<Accept extends `image/${string}`> = {
|
|
16
51
|
/**
|
|
17
52
|
* The accepted mime type pattern. Must be an image type (e.g., "image/png", "image/webp", "image/*")
|
|
18
53
|
* @default "image/*"
|
|
@@ -27,14 +62,7 @@ export type ImagesOptions<Accept extends `image/${string}`> = {
|
|
|
27
62
|
* gallery that had simply not said where it wanted its files silently shared a
|
|
28
63
|
* directory with every other one.
|
|
29
64
|
*/
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Alt text schema. Can be:
|
|
33
|
-
* - s.string() for required alt text
|
|
34
|
-
* - s.string().nullable() for optional alt text (default)
|
|
35
|
-
* - s.record(s.string(), s.string()) for locale-based alt text
|
|
36
|
-
*/
|
|
37
|
-
alt?: AltSchema;
|
|
65
|
+
dir: "/public" | `/public/${string}`;
|
|
38
66
|
/**
|
|
39
67
|
* Re-encode uploads in the browser before they are uploaded.
|
|
40
68
|
*
|
|
@@ -44,30 +72,35 @@ export type ImagesOptions<Accept extends `image/${string}`> = {
|
|
|
44
72
|
encode?: ImageEncodeOption;
|
|
45
73
|
};
|
|
46
74
|
/**
|
|
47
|
-
* Metadata for an image entry in the images record
|
|
75
|
+
* Metadata for an image entry in the images record.
|
|
76
|
+
*
|
|
77
|
+
* `alt` follows the schema's `alt` option, so it defaults to `string | null`
|
|
78
|
+
* and becomes `string` under `s.string()` or `Record<string, string>` under
|
|
79
|
+
* `s.record(s.string())`. Write `ImagesetEntryMetadata<AltSource>` where a
|
|
80
|
+
* gallery of any alt shape is acceptable.
|
|
48
81
|
*/
|
|
49
|
-
export type
|
|
82
|
+
export type ImagesetEntryMetadata<Alt extends AltSource = string | null> = {
|
|
50
83
|
width: number;
|
|
51
84
|
height: number;
|
|
52
85
|
mimeType: string;
|
|
53
|
-
alt:
|
|
86
|
+
alt: Alt;
|
|
54
87
|
hotspot?: {
|
|
55
88
|
x: number;
|
|
56
89
|
y: number;
|
|
57
90
|
};
|
|
58
91
|
};
|
|
59
|
-
export type
|
|
60
|
-
type
|
|
92
|
+
export type SerializedImagesetSchema = SerializedRecordSchema;
|
|
93
|
+
type ImagesetItemProps<Alt extends AltSchema> = {
|
|
61
94
|
width: NumberSchema<number>;
|
|
62
95
|
height: NumberSchema<number>;
|
|
63
96
|
mimeType: StringSchema<string>;
|
|
64
|
-
alt:
|
|
97
|
+
alt: Alt;
|
|
65
98
|
};
|
|
66
|
-
type
|
|
99
|
+
type ImagesetItemSrc<Alt extends AltSchema> = {
|
|
67
100
|
width: number;
|
|
68
101
|
height: number;
|
|
69
102
|
mimeType: string;
|
|
70
|
-
alt:
|
|
103
|
+
alt: AltSourceOf<Alt>;
|
|
71
104
|
};
|
|
72
105
|
/**
|
|
73
106
|
* Define a collection of images.
|
|
@@ -78,9 +111,9 @@ type ImagesItemSrc = {
|
|
|
78
111
|
*
|
|
79
112
|
* @example
|
|
80
113
|
* ```typescript
|
|
81
|
-
* const schema = s.
|
|
114
|
+
* const schema = s.imageset({
|
|
82
115
|
* accept: "image/webp",
|
|
83
|
-
*
|
|
116
|
+
* dir: "/public/val/images",
|
|
84
117
|
* alt: s.string().minLength(4),
|
|
85
118
|
* });
|
|
86
119
|
* export default c.define("/content/images.val.ts", schema, {
|
|
@@ -93,5 +126,5 @@ type ImagesItemSrc = {
|
|
|
93
126
|
* });
|
|
94
127
|
* ```
|
|
95
128
|
*/
|
|
96
|
-
export declare const
|
|
129
|
+
export declare const imageset: <Accept extends `image/${string}`, Alt extends AltSchema = StringSchema<string | null>>(options: ImagesetOptions<Accept, Alt>) => RecordSchema<ObjectSchema<ImagesetItemProps<Alt>, ImagesetItemSrc<Alt>>, Schema<string>, Record<string, ImagesetEntryMetadata<AltSourceOf<Alt>>>>;
|
|
97
130
|
export {};
|
|
@@ -17,13 +17,14 @@ import { SerializedCodeSchema } from "./code.js";
|
|
|
17
17
|
import { SerializedColorSchema } from "./color.js";
|
|
18
18
|
import { SerializedDateSchema } from "./date.js";
|
|
19
19
|
import { SerializedDateTimeSchema } from "./datetime.js";
|
|
20
|
+
import { SerializedLocaleSchema } from "./locale.js";
|
|
20
21
|
import { SerializedRouteSchema } from "./route.js";
|
|
21
22
|
import { SerializedSettingsSchema } from "./settings.js";
|
|
22
23
|
import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
|
|
23
24
|
import { FileSource } from "../source/media.js";
|
|
24
25
|
import { GenericRichTextSourceNode, RichTextSource } from "../source/richtext.js";
|
|
25
26
|
import { ReifiedPreview, PreviewScope, PreviewItem } from "../preview.js";
|
|
26
|
-
export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedDiscriminatedUnionSchema | SerializedEnumSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedSettingsSchema | SerializedImageSchema;
|
|
27
|
+
export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedDiscriminatedUnionSchema | SerializedEnumSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedLocaleSchema | SerializedSettingsSchema | SerializedImageSchema;
|
|
27
28
|
type Primitives = number | string | boolean | null | FileSource;
|
|
28
29
|
export type AssertError = {
|
|
29
30
|
message: string;
|
|
@@ -74,6 +75,88 @@ export declare abstract class Schema<Src extends SelectorSource> {
|
|
|
74
75
|
* against the real instance. Structural errors publish first; these merge in.
|
|
75
76
|
*/
|
|
76
77
|
protected abstract executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
78
|
+
/**
|
|
79
|
+
* Whether this node is a locale — the field that MARKS a scope, `s.locale()`.
|
|
80
|
+
*
|
|
81
|
+
* Only `LocaleSchema` overrides this. A base implementation rather than an
|
|
82
|
+
* abstract one on purpose: every other schema class answers no, and a
|
|
83
|
+
* question every class had to answer would be twenty edits for one yes.
|
|
84
|
+
*
|
|
85
|
+
* Not the same question as {@link opensLocaleScope}: a locale field marks the
|
|
86
|
+
* scope, the object AROUND it is the scope. See `localeScope.ts`.
|
|
87
|
+
*/
|
|
88
|
+
protected isLocaleField(): boolean;
|
|
89
|
+
/**
|
|
90
|
+
* Whether this node OPENS a locale scope, and what opens it.
|
|
91
|
+
*
|
|
92
|
+
* An object with a `s.locale()` field opens one; so does a record keyed by
|
|
93
|
+
* `s.locale()`. Everything below such a node is in that one language, which
|
|
94
|
+
* is why a scope may not contain another — see `localeScope.ts` for the rule
|
|
95
|
+
* and the reason it is validated rather than typed.
|
|
96
|
+
*/
|
|
97
|
+
protected opensLocaleScope(): "field" | "key" | null;
|
|
98
|
+
/**
|
|
99
|
+
* The child schemas a locale scope reaches, by the path segment that reaches
|
|
100
|
+
* them.
|
|
101
|
+
*
|
|
102
|
+
* A structural walk that deliberately does NOT go through `executeSerialize`:
|
|
103
|
+
* serializing a subtree at every node to ask a question about its shape is
|
|
104
|
+
* quadratic, and this is asked during validation. `*` stands for the item of
|
|
105
|
+
* an array or record, which has no name of its own.
|
|
106
|
+
*/
|
|
107
|
+
protected localeScopeChildren(): {
|
|
108
|
+
key: string;
|
|
109
|
+
schema: Schema<SelectorSource>;
|
|
110
|
+
}[];
|
|
111
|
+
/**
|
|
112
|
+
* Every locale scope opened strictly BELOW this node, by path.
|
|
113
|
+
*
|
|
114
|
+
* Stops descending at each one it finds, so a scope three deep is reported
|
|
115
|
+
* once, by the scope immediately enclosing it, rather than by every ancestor.
|
|
116
|
+
*/
|
|
117
|
+
protected localeScopesBelow(prefix?: string[]): {
|
|
118
|
+
path: string[];
|
|
119
|
+
kind: "field" | "key";
|
|
120
|
+
}[];
|
|
121
|
+
/**
|
|
122
|
+
* The scope rule, as errors at this node — or `false` if it is not broken.
|
|
123
|
+
*
|
|
124
|
+
* Two things are wrong and both are wrong in the SCHEMA rather than in the
|
|
125
|
+
* content, so both are `schemaError`s: an object with two locale fields (the
|
|
126
|
+
* subtree below it would be in two languages at once), and a locale scope
|
|
127
|
+
* inside another (the inner one would silently override the outer for part
|
|
128
|
+
* of a subtree that is supposed to be one language throughout).
|
|
129
|
+
*
|
|
130
|
+
* Validated rather than typed on purpose. Expressing "no scope below this
|
|
131
|
+
* one" as a constraint means threading it through every schema class's type
|
|
132
|
+
* parameter, and the errors that fall out of a recursive constraint like
|
|
133
|
+
* that name the whole tree — an unrelated typo in a `.val.ts` would print
|
|
134
|
+
* pages. See the design notes on the locales PR.
|
|
135
|
+
*/
|
|
136
|
+
protected localeScopeErrors(): ValidationError[];
|
|
137
|
+
/**
|
|
138
|
+
* The answer, computed once per schema.
|
|
139
|
+
*
|
|
140
|
+
* Memoised because the question is asked from `executeValidate`, which runs
|
|
141
|
+
* once per SOURCE instance: an array of five hundred blocks asks the item
|
|
142
|
+
* schema five hundred times, and each answer walks the schema below it
|
|
143
|
+
* looking for a nested scope. The answer cannot differ between two instances
|
|
144
|
+
* of the same schema — nothing here reads the source — so the walk is done
|
|
145
|
+
* once and the result handed back after that.
|
|
146
|
+
*
|
|
147
|
+
* The error is still reported at every instance's path. Val's validation
|
|
148
|
+
* errors are keyed by source path and that is where the Studio draws them, so
|
|
149
|
+
* a schema error on a row is a schema error on every row; reporting it on one
|
|
150
|
+
* arbitrary row would put it somewhere nobody was looking.
|
|
151
|
+
*/
|
|
152
|
+
private localeScopeErrorsMemo?;
|
|
153
|
+
private computeLocaleScopeErrors;
|
|
154
|
+
/**
|
|
155
|
+
* This node's own `s.locale()` fields, where it is an object that has any.
|
|
156
|
+
*
|
|
157
|
+
* Empty everywhere else, so `localeScopeErrors` can live on the base class.
|
|
158
|
+
*/
|
|
159
|
+
protected localeFieldNames(): string[];
|
|
77
160
|
protected executeCustomValidateFunctions(src: Src, customValidateFunctions: CustomValidateFunction<Src>[], ctx: {
|
|
78
161
|
path: SourcePath;
|
|
79
162
|
}): ValidationError[];
|
|
@@ -99,8 +182,38 @@ export declare abstract class Schema<Src extends SelectorSource> {
|
|
|
99
182
|
* The editor gets a way to clear the field, and the type of the source
|
|
100
183
|
* widens to include `null` — so consuming code has to handle it.
|
|
101
184
|
*
|
|
102
|
-
*
|
|
103
|
-
* validator
|
|
185
|
+
* `.validate(...)` may be declared before or after `.nullable()`: the
|
|
186
|
+
* validator is carried over either way. Declared before, its argument is
|
|
187
|
+
* typed as non-null even though `null` can reach it, so guard for it.
|
|
188
|
+
*
|
|
189
|
+
* Implementations MUST carry `customValidateFunctions` over to the new
|
|
190
|
+
* instance. `.nullable()` returns a copy, so dropping them there silently
|
|
191
|
+
* un-declares the user's `.validate(...)` whenever it was written before the
|
|
192
|
+
* `.nullable()` — which is the order most people write it in. Thirteen schema
|
|
193
|
+
* classes passed `[]` here until this was fixed; `nullableCustomValidate.test.ts`
|
|
194
|
+
* pins one instance of every factory on `s` against that, and does not compile
|
|
195
|
+
* until a newly added schema is listed in it.
|
|
196
|
+
*
|
|
197
|
+
* The validators keep running when the value IS null: a nullable schema's
|
|
198
|
+
* validator sees `Src | null` and decides for itself. That is what the
|
|
199
|
+
* classes that never dropped them (string, record, route, file, image) have
|
|
200
|
+
* always done.
|
|
201
|
+
*
|
|
202
|
+
* Carrying them over needs a cast, because `Src` sits in a PARAMETER position
|
|
203
|
+
* of {@link CustomValidateFunction} and so `CustomValidateFunction<Src>[]` is
|
|
204
|
+
* not assignable to `CustomValidateFunction<Src | null>[]`. Widening what the
|
|
205
|
+
* functions can be CALLED with is the intent here rather than something the
|
|
206
|
+
* cast gets away with: `null` is handed to them, and the paragraph above is
|
|
207
|
+
* the behaviour that buys.
|
|
208
|
+
*
|
|
209
|
+
* A validator's parameter type was never a runtime guarantee to begin with.
|
|
210
|
+
* `executeValidate` runs the custom validators BEFORE the structural checks
|
|
211
|
+
* (see {@link CustomValidateFunction}'s callers, e.g. `NumberSchema`, which
|
|
212
|
+
* calls them ahead of its `typeof src !== "number"`), so one can already be
|
|
213
|
+
* called with a value of the wrong type entirely — hand-written content, or
|
|
214
|
+
* a node the Studio's walker reached before its type was checked. That is why
|
|
215
|
+
* {@link executeCustomValidateFunctions} catches what a validator throws and
|
|
216
|
+
* reports it as a `schemaError` instead of letting it escape.
|
|
104
217
|
*
|
|
105
218
|
* @example
|
|
106
219
|
* const schema = s.object({
|
|
@@ -25,6 +25,8 @@ export declare class LiteralSchema<Src extends string | null> extends Schema<Src
|
|
|
25
25
|
private readonly description?;
|
|
26
26
|
private readonly renderInput;
|
|
27
27
|
private readonly previewInput;
|
|
28
|
+
/** Type-only marker: as a record key, this declares the key set. See `LocaleSchema`. */
|
|
29
|
+
readonly __declaresRecordKeys: true;
|
|
28
30
|
constructor(value: string, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
|
|
29
31
|
/**
|
|
30
32
|
* Describe this field.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
+
import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
|
|
3
|
+
import { FieldRender } from "../render.js";
|
|
4
|
+
import { SourcePath } from "../val/index.js";
|
|
5
|
+
import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
|
|
6
|
+
export type SerializedLocaleSchema = {
|
|
7
|
+
type: "locale";
|
|
8
|
+
/** Static layout config, carried whole in the serialized schema — see `render.ts`. */
|
|
9
|
+
render?: FieldRender;
|
|
10
|
+
/** Set when this schema declares a `preview`. The closure itself cannot serialize. */
|
|
11
|
+
preview?: true;
|
|
12
|
+
opt: boolean;
|
|
13
|
+
customValidate?: boolean;
|
|
14
|
+
readonly?: boolean;
|
|
15
|
+
hidden?: boolean;
|
|
16
|
+
description?: string;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* One of the languages the project publishes.
|
|
20
|
+
*
|
|
21
|
+
* The set is NOT here. It is declared in the settings module — `locales.available`
|
|
22
|
+
* — so that adding a language is something the people who write the content can
|
|
23
|
+
* do, and so that one list drives validation, the Studio's picker and the
|
|
24
|
+
* translation notes. This schema knows only that the value is one of them, which
|
|
25
|
+
* is checked cross-module through the `locale:check-locale` fix, exactly as
|
|
26
|
+
* `keyOf` and `route` check theirs.
|
|
27
|
+
*
|
|
28
|
+
* Never stega encoded (see `stegaEncode.ts`): a locale goes into `<html lang>`,
|
|
29
|
+
* into `hreflang` and into `Intl` constructors, none of which survive invisible
|
|
30
|
+
* characters.
|
|
31
|
+
*/
|
|
32
|
+
export declare class LocaleSchema<Src extends string | null> extends Schema<Src> {
|
|
33
|
+
private readonly opt;
|
|
34
|
+
private readonly customValidateFunctions;
|
|
35
|
+
private readonly isReadonly;
|
|
36
|
+
private readonly isHidden;
|
|
37
|
+
private readonly description?;
|
|
38
|
+
private readonly renderInput;
|
|
39
|
+
private readonly previewInput;
|
|
40
|
+
/**
|
|
41
|
+
* Type-only marker: as a record key, this schema DECLARES the key set.
|
|
42
|
+
*
|
|
43
|
+
* `declare` so it exists in the type and nowhere at runtime — reading it off
|
|
44
|
+
* an instance gives `undefined`, and nothing should. It is how `record()`
|
|
45
|
+
* knows to type entries as `Item | null`: a language nobody has translated
|
|
46
|
+
* into is a null entry, not an absent key. See `DeclaredKeySet` and
|
|
47
|
+
* `RecordSrcOf`.
|
|
48
|
+
*/
|
|
49
|
+
readonly __declaresRecordKeys: true;
|
|
50
|
+
constructor(opt?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
|
|
51
|
+
/**
|
|
52
|
+
* A note to whoever edits this field, shown under it in the Studio.
|
|
53
|
+
*
|
|
54
|
+
* Pass `null` to clear a description set earlier.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* const schema = s.object({
|
|
58
|
+
* locale: s.locale().describe("The language everything below is written in"),
|
|
59
|
+
* title: s.string(),
|
|
60
|
+
* });
|
|
61
|
+
* export default c.define("/example.val.ts", schema, {
|
|
62
|
+
* locale: "nb-NO",
|
|
63
|
+
* title: "Vinterjakke",
|
|
64
|
+
* });
|
|
65
|
+
*/
|
|
66
|
+
describe(description: string | null): LocaleSchema<Src>;
|
|
67
|
+
/**
|
|
68
|
+
* A check of your own, on top of "is this one of the project's languages".
|
|
69
|
+
*
|
|
70
|
+
* Return a message when the value is WRONG and `false` when it is fine — the
|
|
71
|
+
* two answers are a complaint and no complaint, so write the check as a
|
|
72
|
+
* ternary rather than as `ok || "message"`, which returns `true` and `true`
|
|
73
|
+
* is neither.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* const schema = s.object({
|
|
77
|
+
* locale: s.locale().validate((val) =>
|
|
78
|
+
* val === "en-US" ? "This section is not translated into English yet" : false,
|
|
79
|
+
* ),
|
|
80
|
+
* title: s.string(),
|
|
81
|
+
* });
|
|
82
|
+
* export default c.define("/example.val.ts", schema, {
|
|
83
|
+
* locale: "nb-NO",
|
|
84
|
+
* title: "Vinterjakke",
|
|
85
|
+
* });
|
|
86
|
+
*/
|
|
87
|
+
validate(validationFunction: (src: Src) => false | string): LocaleSchema<Src>;
|
|
88
|
+
protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
|
|
89
|
+
protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
|
|
90
|
+
nullable(): LocaleSchema<Src | null>;
|
|
91
|
+
readonly(): LocaleSchema<Src>;
|
|
92
|
+
hidden(): LocaleSchema<Src>;
|
|
93
|
+
protected isLocaleField(): boolean;
|
|
94
|
+
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
95
|
+
/**
|
|
96
|
+
* How this field is laid out where it is the item of an array or a record.
|
|
97
|
+
* Static configuration, not a callback — see `render.ts`.
|
|
98
|
+
*
|
|
99
|
+
* @example
|
|
100
|
+
* const schema = s.array(s.locale().render({ as: "inline" }));
|
|
101
|
+
* export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
|
|
102
|
+
*/
|
|
103
|
+
render(input: FieldRender): LocaleSchema<Src>;
|
|
104
|
+
/**
|
|
105
|
+
* How this VALUE is shown where a preview of it is needed. Never how the field
|
|
106
|
+
* itself is edited (that is `render`). See `preview.ts`.
|
|
107
|
+
*
|
|
108
|
+
* @example
|
|
109
|
+
* const schema = s.array(
|
|
110
|
+
* s.locale().preview(({ val }) => ({ title: val })),
|
|
111
|
+
* );
|
|
112
|
+
* export default c.define("/example.val.ts", schema, ["nb-NO", "en-US"]);
|
|
113
|
+
*/
|
|
114
|
+
preview(select: ItemPreviewInput<Src>): LocaleSchema<Src>;
|
|
115
|
+
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
116
|
+
protected declaresItemPreview(): boolean;
|
|
117
|
+
protected executeSerialize(): SerializedSchema;
|
|
118
|
+
protected executePreview(): ReifiedPreview;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Define one of the project's languages.
|
|
122
|
+
*
|
|
123
|
+
* The languages themselves are declared in the settings module, under
|
|
124
|
+
* `locales.available` — this says only that a value is one of them.
|
|
125
|
+
*
|
|
126
|
+
* @example // a field: everything under this object is in this language
|
|
127
|
+
* const schema = s.object({ locale: s.locale(), title: s.string() });
|
|
128
|
+
*
|
|
129
|
+
* @example // a key: one entry per language
|
|
130
|
+
* const schema = s.record(s.locale(), s.object({ title: s.string() }));
|
|
131
|
+
*/
|
|
132
|
+
export declare const locale: () => LocaleSchema<string>;
|
|
@@ -102,6 +102,13 @@ export declare class ObjectSchema<Props extends ObjectSchemaProps, Src extends O
|
|
|
102
102
|
nullable(): ObjectSchema<Props, Src | null>;
|
|
103
103
|
readonly(isReadonly?: boolean): ObjectSchema<Props, Src>;
|
|
104
104
|
hidden(isHidden?: boolean): ObjectSchema<Props, Src>;
|
|
105
|
+
/** The names of this object's `s.locale()` fields, in declaration order. */
|
|
106
|
+
protected localeFieldNames(): string[];
|
|
107
|
+
protected opensLocaleScope(): "field" | "key" | null;
|
|
108
|
+
protected localeScopeChildren(): {
|
|
109
|
+
key: string;
|
|
110
|
+
schema: Schema<SelectorSource>;
|
|
111
|
+
}[];
|
|
105
112
|
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
106
113
|
/**
|
|
107
114
|
* How this field is laid out in the editor when it is the item of an array
|