@valbuild/core 0.109.0 → 0.111.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.
@@ -4,7 +4,7 @@ export { extractValModules, computeValModuleShas, type ExtractedValModules, type
4
4
  export type { InitVal, ValConfig, ValConstructor, ContentConstructor, } from "./initVal.js";
5
5
  export { Schema, type SerializedSchema, type SelectorOfSchema } from "./schema/index.js";
6
6
  export { hasRemoteFileSchema } from "./schema/hasRemoteFileSchema.js";
7
- export type { ImageMetadata } from "./schema/image.js";
7
+ export type { ImageMetadata, ImageEncodeOption, ImageEncodeOptions, } from "./schema/image.js";
8
8
  export type { FileMetadata } from "./schema/file.js";
9
9
  export type { ValModule, SerializedModule, InferValModuleType } from "./module.js";
10
10
  export type { SourceObject, SourcePrimitive, Source } from "./source/index.js";
@@ -51,6 +51,7 @@ export { type SerializedImageSchema, ImageSchema } from "./schema/image.js";
51
51
  export { type SerializedFileSchema, FileSchema } from "./schema/file.js";
52
52
  export { type SerializedDateSchema, DateSchema } from "./schema/date.js";
53
53
  export { type SerializedColorSchema, type ColorOptions, ColorSchema, } from "./schema/color.js";
54
+ export { type SerializedCodeSchema, type CodeOptions, type CodeLanguage, CodeSchema, CODE_LANGUAGES, } from "./schema/code.js";
54
55
  export { type ColorFormat, type ParsedColor, COLOR_FORMATS, DEFAULT_COLOR_FORMAT, } from "./schema/colorFormat.js";
55
56
  export { type SerializedDateTimeSchema, DateTimeSchema, } from "./schema/datetime.js";
56
57
  export { type SerializedKeyOfSchema, KeyOfSchema } from "./schema/keyOf.js";
@@ -60,7 +61,7 @@ export { type SerializedUnionSchema, UnionSchema, type SerializedStringUnionSche
60
61
  export { type SerializedLiteralSchema, LiteralSchema } from "./schema/literal.js";
61
62
  export { deserializeSchema } from "./schema/deserialize.js";
62
63
  export { type PreviewItem, type ItemPreviewInput, type RecordPreview, type ArrayPreview, type ReifiedPreview, type PreviewScope, previewScope, } from "./preview.js";
63
- export { type CodeLanguage, type StringRender, type InlineRender, type FieldRender, CODE_LANGUAGES, } from "./render.js";
64
+ export { type InlineRender, type FieldRender, isInlineRender } from "./render.js";
64
65
  export type { ValRouter, RouteValidationError } from "./router.js";
65
66
  export { getSourcePathFromRoute } from "./getSourcePathFromRoute.js";
66
67
  export declare const FATAL_ERROR_TYPES: readonly ["no-schema", "no-source", "invalid-id", "no-module", "invalid-patch"];
@@ -13,6 +13,7 @@ import { file } from "./schema/file.js";
13
13
  import { files } from "./schema/files.js";
14
14
  import { date } from "./schema/date.js";
15
15
  import { datetime } from "./schema/datetime.js";
16
+ import { code } from "./schema/code.js";
16
17
  import { color } from "./schema/color.js";
17
18
  import { route } from "./schema/route.js";
18
19
  import { router } from "./schema/router.js";
@@ -206,6 +207,23 @@ export type InitSchema = {
206
207
  *
207
208
  */
208
209
  readonly color: typeof color;
210
+ /**
211
+ * Define a string edited in a code editor.
212
+ *
213
+ * Pass a `language` to syntax highlight it; leave it out for a plain
214
+ * monospaced editor. The value is a plain string, and — unlike `s.string()` —
215
+ * it is never stega encoded, so the code reaches your app as it was written.
216
+ *
217
+ * @example
218
+ * const schema = s.code({ language: "typescript" });
219
+ * export default c.define("/example.val.ts", schema, "const a = 1;");
220
+ *
221
+ * @example
222
+ * const schema = s.code();
223
+ * export default c.define("/example.val.ts", schema, "no highlighting here");
224
+ *
225
+ */
226
+ readonly code: typeof code;
209
227
  /**
210
228
  * Define a string that references a route path in your application.
211
229
  *
@@ -317,6 +335,7 @@ export declare function initSchema(): {
317
335
  date: (options?: Record<string, never>) => import("./index.js").DateSchema<import("./index.js").RawString>;
318
336
  datetime: (options?: Record<string, never>) => import("./index.js").DateTimeSchema<import("./index.js").RawString>;
319
337
  color: (options?: import("./index.js").ColorOptions) => import("./index.js").ColorSchema<import("./index.js").RawString>;
338
+ code: (options?: import("./index.js").CodeOptions) => import("./index.js").CodeSchema<import("./index.js").RawString>;
320
339
  route: <T extends string>(options?: Record<string, never>) => import("./index.js").RouteSchema<T>;
321
340
  router: typeof router;
322
341
  images: <Accept extends `image/${string}`>(options: import("./schema/images.js").ImagesOptions<Accept>) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
@@ -1,9 +1,14 @@
1
+ import type { SerializedSchema } from "./schema/index.js";
1
2
  /**
2
3
  * A RENDER is how the FIELD ITSELF is laid out in the editor, and it applies
3
- * only when you are looking at the field: `s.string().render({ as: "textarea"
4
- * })`, `.render({ as: "code", language })`, `.render({ as: "inline" })` on an
4
+ * only when you are looking at the field: `.render({ as: "inline" })` on an
5
5
  * array/record item.
6
6
  *
7
+ * It is deliberately the ONE thing a render can say. What a string looks like
8
+ * when it holds more than a line is the schema's own business — `.multiline()`
9
+ * for a text box, `s.code({ language })` for a code editor — not a layout
10
+ * bolted onto `s.string()` from outside.
11
+ *
7
12
  * A PREVIEW (`preview.ts`) is the other thing: how the VALUE is shown wherever
8
13
  * a preview of it is needed — a list row, a reference dropdown, a search hit —
9
14
  * that is, wherever the value is navigable to rather than open. The two never
@@ -24,13 +29,6 @@
24
29
  * `render` its own execute/store pair again. It is NOT to re-merge the two:
25
30
  * conflating them is what this file was split up to undo.
26
31
  */
27
- /**
28
- * The list is the declaration and {@link CodeLanguage} is derived from it, so
29
- * that a validator elsewhere (`shared`'s zod schema) can enumerate the same
30
- * languages without a second copy that drifts. Same shape as `COLOR_FORMATS`.
31
- */
32
- export declare const CODE_LANGUAGES: readonly ["typescript", "javascript", "javascriptreact", "typescriptreact", "json", "java", "html", "css", "xml", "markdown", "sql", "python", "rust", "php", "go", "cpp", "sass", "vue", "angular"];
33
- export type CodeLanguage = (typeof CODE_LANGUAGES)[number];
34
32
  /**
35
33
  * `{ as: "inline" }` on a field that is the ITEM of an array or record: the
36
34
  * container renders the field itself inside each (sortable) row, instead of a
@@ -48,18 +46,32 @@ export type InlineRender = {
48
46
  as: "inline";
49
47
  };
50
48
  /**
51
- * What `.render(...)` takes on every field except `s.string()`, and what the
52
- * serialized schema carries verbatim.
49
+ * What `.render(...)` takes on every field, and what the serialized schema
50
+ * carries verbatim.
53
51
  */
54
52
  export type FieldRender = InlineRender;
55
53
  /**
56
- * What `s.string().render(...)` takes, and what the serialized schema carries
57
- * verbatim. A string has editor layouts of its own (textarea, code) in
58
- * addition to the container-facing `inline`.
54
+ * Is this item schema edited INSIDE its list row, rather than behind a
55
+ * clickable preview row that navigates to it?
56
+ *
57
+ * The one answer, so that the list rows, the nav-stop rule (`getNavPath`) and
58
+ * the add buttons cannot drift apart — they are three readings of the same
59
+ * question, and a disagreement between them is a row you can edit in place but
60
+ * that "add" navigates away from.
61
+ *
62
+ * A tagged union counts as inline when the union itself declares it OR when
63
+ * ANY of its variants does. A page-builder list is `s.array(s.union("type",
64
+ * block, block, ...))` and the natural place to write the render is on the
65
+ * blocks, one per block type — the union is a dispatch, not something the
66
+ * author thinks of as the field. `some` rather than `every` because the row
67
+ * draws the union's own editor (the tag selector plus the matched variant's
68
+ * fields), which handles every variant either way: with `every`, adding one
69
+ * variant and forgetting its `.render` would silently turn the whole list back
70
+ * into preview rows.
71
+ *
72
+ * This is the ONLY place a render is allowed to be read from anywhere but the
73
+ * schema it was declared on. It stays static (see the top of this file): the
74
+ * answer is a function of the serialized schema alone, never of the value the
75
+ * row happens to hold.
59
76
  */
60
- export type StringRender = {
61
- as: "textarea";
62
- } | {
63
- as: "code";
64
- language: CodeLanguage;
65
- } | InlineRender;
77
+ export declare function isInlineRender(schema: SerializedSchema): boolean;
@@ -0,0 +1,85 @@
1
+ import { CustomValidateFunction, 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 { RawString } from "./string.js";
6
+ import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
7
+ /**
8
+ * The list is the declaration and {@link CodeLanguage} is derived from it, so
9
+ * that a validator elsewhere (`shared`'s zod schema) can enumerate the same
10
+ * languages without a second copy that drifts. Same shape as `COLOR_FORMATS`.
11
+ */
12
+ export declare const CODE_LANGUAGES: readonly ["typescript", "javascript", "javascriptreact", "typescriptreact", "json", "java", "html", "css", "xml", "markdown", "sql", "python", "rust", "php", "go", "cpp", "sass", "vue", "angular"];
13
+ export type CodeLanguage = (typeof CODE_LANGUAGES)[number];
14
+ export type CodeOptions = {
15
+ /**
16
+ * The language to syntax highlight the editor with.
17
+ *
18
+ * Omit it for a plain monospaced editor with no highlighting.
19
+ *
20
+ * @example
21
+ * "typescript"
22
+ * "json"
23
+ * "markdown"
24
+ */
25
+ language?: CodeLanguage;
26
+ };
27
+ export type SerializedCodeSchema = {
28
+ type: "code";
29
+ /** Static layout config, carried whole in the serialized schema — see `render.ts`. */
30
+ render?: FieldRender;
31
+ /** Set when this schema declares a `preview`. The closure itself cannot serialize. */
32
+ preview?: true;
33
+ options?: CodeOptions;
34
+ opt: boolean;
35
+ customValidate?: boolean;
36
+ readonly?: boolean;
37
+ hidden?: boolean;
38
+ description?: string;
39
+ };
40
+ /**
41
+ * A string edited in a code editor.
42
+ *
43
+ * Its own schema type rather than a layout on `s.string()`: the language is
44
+ * part of what the content IS, not of how one field happens to be drawn, and
45
+ * being a type is what lets the value stay out of stega encoding — invisible
46
+ * characters woven into source code are not something a consumer can render.
47
+ */
48
+ export declare class CodeSchema<Src extends string | null> extends Schema<Src> {
49
+ private readonly options?;
50
+ private readonly opt;
51
+ private readonly customValidateFunctions;
52
+ private readonly isReadonly;
53
+ private readonly isHidden;
54
+ private readonly description?;
55
+ private readonly renderInput;
56
+ private readonly previewInput;
57
+ constructor(options?: CodeOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
58
+ describe(description: string | null): CodeSchema<Src>;
59
+ validate(validationFunction: (src: Src) => false | string): CodeSchema<Src>;
60
+ protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
61
+ protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
62
+ protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
63
+ nullable(): CodeSchema<Src | null>;
64
+ readonly(): CodeSchema<Src>;
65
+ hidden(): CodeSchema<Src>;
66
+ /**
67
+ * How this field is laid out in the editor when it is the item of an array
68
+ * or record: `{ as: "inline" }` renders the field itself inside each row,
69
+ * instead of a preview row that navigates to it.
70
+ *
71
+ * Static configuration, not a callback — see `render.ts`.
72
+ */
73
+ render(input: FieldRender): CodeSchema<Src>;
74
+ /**
75
+ * How this VALUE is shown where a preview of it is needed — a row in a
76
+ * sortable list, a reference dropdown, a search hit. Never how the field
77
+ * itself is edited (that is `render`). See `preview.ts`.
78
+ */
79
+ preview(select: ItemPreviewInput<Src>): CodeSchema<Src>;
80
+ protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
81
+ protected declaresItemPreview(): boolean;
82
+ protected executeSerialize(): SerializedSchema;
83
+ protected executePreview(): ReifiedPreview;
84
+ }
85
+ export declare const code: (options?: CodeOptions) => CodeSchema<RawString>;
@@ -6,11 +6,44 @@ import { ValModule } from "../index.js";
6
6
  import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
7
7
  import { FieldRender } from "../render.js";
8
8
  import { ImagesEntryMetadata } from "./images.js";
9
+ /**
10
+ * How an uploaded image is re-encoded in the browser, before it is uploaded.
11
+ *
12
+ * Off unless a schema asks for it. When it is on, the image is converted to
13
+ * `type` and scaled down to fit `maxWidth` x `maxHeight` - unless the result
14
+ * would be BIGGER than the original and no downscale was needed, in which case
15
+ * the original bytes are kept. See `architecture/media.md`.
16
+ *
17
+ * `type` is required so that adding a format later is additive: a schema
18
+ * written today keeps saying exactly which format it asked for.
19
+ */
20
+ export type ImageEncodeOptions = {
21
+ type: "webp";
22
+ /** Passed to `canvas.toBlob`. Between 0 and 1. @default 0.8 */
23
+ quality?: number;
24
+ /** @default 2560 */
25
+ maxWidth?: number;
26
+ /** @default 2560 */
27
+ maxHeight?: number;
28
+ };
29
+ /** `false` (or absent) uploads the bytes exactly as the editor picked them. */
30
+ export type ImageEncodeOption = false | ImageEncodeOptions;
31
+ /**
32
+ * What a GALLERY-BACKED field may say for itself.
33
+ *
34
+ * Not `ImageOptions`: `directory` and `accept` belong to the gallery, and a
35
+ * field repeating them is how two copies of one fact get to disagree. `encode`
36
+ * is different — it describes what happens to the bytes on their way IN, so a
37
+ * field that wants the original where its gallery re-encodes has to be able to
38
+ * say `encode: false`, and there is nowhere else to say it.
39
+ */
40
+ export type GalleryImageOptions = {
41
+ encode?: ImageEncodeOption;
42
+ };
9
43
  export type ImageOptions = {
10
- ext?: ["jpg"] | ["webp"];
11
44
  directory?: string;
12
- prefix?: string;
13
45
  accept?: string;
46
+ encode?: ImageEncodeOption;
14
47
  };
15
48
  export type SerializedImageSchema = {
16
49
  type: "image";
@@ -86,6 +119,6 @@ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<
86
119
  * An image picked from a gallery. Its dimensions and mime type live in the
87
120
  * gallery, so the field carries only what a person typed.
88
121
  */
89
- export declare function image(galleryModule: ValModule<Record<string, ImagesEntryMetadata>>): ImageSchema<GalleryImageSource>;
122
+ export declare function image(galleryModule: ValModule<Record<string, ImagesEntryMetadata>>, galleryOptions?: GalleryImageOptions): ImageSchema<GalleryImageSource>;
90
123
  /** An image of its own, carrying its own dimensions and mime type. */
91
124
  export declare function image(options?: ImageOptions): ImageSchema<ImageSource>;
@@ -4,6 +4,7 @@ import { RecordSchema } from "./record.js";
4
4
  import { ObjectSchema } from "./object.js";
5
5
  import { StringSchema } from "./string.js";
6
6
  import { NumberSchema } from "./number.js";
7
+ import type { ImageEncodeOption } from "./image.js";
7
8
  /**
8
9
  * Alt schema type - can be a string, nullable string, or a record of locale to string
9
10
  */
@@ -39,6 +40,13 @@ export type ImagesOptions<Accept extends `image/${string}`> = {
39
40
  * @default false
40
41
  */
41
42
  remote?: boolean;
43
+ /**
44
+ * Re-encode uploads in the browser before they are uploaded.
45
+ *
46
+ * Off unless set. A field backed by this gallery (`s.image(galleryVal)`)
47
+ * inherits it, the same way it inherits `accept` and `directory`.
48
+ */
49
+ encode?: ImageEncodeOption;
42
50
  };
43
51
  /**
44
52
  * Metadata for an image entry in the images record
@@ -12,6 +12,7 @@ import { SerializedRecordSchema } from "./record.js";
12
12
  import { SerializedRichTextSchema } from "./richtext.js";
13
13
  import { RawString, SerializedStringSchema } from "./string.js";
14
14
  import { SerializedUnionSchema } from "./union.js";
15
+ import { SerializedCodeSchema } from "./code.js";
15
16
  import { SerializedColorSchema } from "./color.js";
16
17
  import { SerializedDateSchema } from "./date.js";
17
18
  import { SerializedDateTimeSchema } from "./datetime.js";
@@ -20,7 +21,7 @@ import { ValidationError, ValidationErrors } from "./validation/ValidationError.
20
21
  import { FileSource } from "../source/media.js";
21
22
  import { GenericRichTextSourceNode, RichTextSource } from "../source/richtext.js";
22
23
  import { ReifiedPreview, PreviewScope, PreviewItem } from "../preview.js";
23
- export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedUnionSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedRouteSchema | SerializedImageSchema;
24
+ export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedUnionSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedImageSchema;
24
25
  type Primitives = number | string | boolean | null | FileSource;
25
26
  export type AssertError = {
26
27
  message: string;
@@ -6,12 +6,15 @@ import { SelectorSource } from "../selector/index.js";
6
6
  import { JsonOf, JsonSource } from "../source/json.js";
7
7
  import { ModuleFilePath, SourcePath } from "../val/index.js";
8
8
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
9
+ import type { ImageEncodeOption } from "./image.js";
9
10
  type MediaOptions = {
10
11
  type: "files" | "images";
11
12
  accept: string;
12
13
  directory: string;
13
14
  remote: boolean;
14
15
  altSchema?: Schema<SelectorSource>;
16
+ /** Images only: how uploads are re-encoded in the browser. See `image.ts`. */
17
+ encode?: ImageEncodeOption;
15
18
  };
16
19
  export type SerializedRecordSchema = {
17
20
  type: "record";
@@ -32,6 +35,7 @@ export type SerializedRecordSchema = {
32
35
  accept?: string;
33
36
  directory?: string;
34
37
  remote?: boolean;
38
+ encode?: ImageEncodeOption;
35
39
  alt?: SerializedSchema;
36
40
  jsonValues?: boolean;
37
41
  readonly?: boolean;
@@ -1,6 +1,6 @@
1
1
  import { Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
2
2
  import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
3
- import { StringRender } from "../render.js";
3
+ import { FieldRender } from "../render.js";
4
4
  import { SourcePath } from "../val/index.js";
5
5
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
6
6
  type StringOptions = {
@@ -20,7 +20,17 @@ export type SerializedStringSchema = {
20
20
  * off the schema it already has. See `render.ts` for what that assumption
21
21
  * buys, and what to do if a render ever needs to stop being static.
22
22
  */
23
- render?: StringRender;
23
+ render?: FieldRender;
24
+ /**
25
+ * Set by `.multiline()`: the field is a growing text box rather than a
26
+ * single-line input.
27
+ *
28
+ * A property of the schema rather than a `render` variant, because it says
29
+ * what the string IS — text that may hold line breaks — and not merely how one
30
+ * field is drawn. It is read the same way a render is, straight off the
31
+ * serialized schema where the field is drawn.
32
+ */
33
+ multiline?: boolean;
24
34
  /** Set when this schema declares a `preview`. The closure itself cannot serialize. */
25
35
  preview?: true;
26
36
  options?: {
@@ -54,7 +64,8 @@ export declare class StringSchema<Src extends string | null> extends Schema<Src>
54
64
  private readonly isHidden;
55
65
  private readonly description?;
56
66
  private readonly previewInput;
57
- constructor(options?: StringOptions | undefined, opt?: boolean, isRaw?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], renderInput?: StringRender | null, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, previewInput?: ItemPreviewInput<Src> | null);
67
+ private readonly isMultiline;
68
+ constructor(options?: StringOptions | undefined, opt?: boolean, isRaw?: boolean, customValidateFunctions?: ((src: Src) => false | string)[], renderInput?: FieldRender | null, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, previewInput?: ItemPreviewInput<Src> | null, isMultiline?: boolean);
58
69
  describe(description: string | null): StringSchema<Src>;
59
70
  /**
60
71
  * @deprecated Use `minLength` instead
@@ -77,13 +88,27 @@ export declare class StringSchema<Src extends string | null> extends Schema<Src>
77
88
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
78
89
  protected executeSerialize(): SerializedSchema;
79
90
  /**
80
- * How this field is laid out in the editor: a textarea, or a code editor for
81
- * the given language.
91
+ * This string holds text that may run to several lines: the editor gives it a
92
+ * growing text box instead of a single-line input.
93
+ *
94
+ * Nothing else changes — the value is still a plain string, and no validation
95
+ * is added or removed. For code, use `s.code({ language })` instead, which is
96
+ * its own schema type.
97
+ *
98
+ * @example
99
+ * const schema = s.string().multiline();
100
+ * export default c.define("/example.val.ts", schema, "Line one\nLine two");
101
+ */
102
+ multiline(): StringSchema<Src>;
103
+ /**
104
+ * How this field is laid out in the editor when it is the item of an array or
105
+ * record: `{ as: "inline" }` renders the field itself inside each row,
106
+ * instead of a preview row that navigates to it.
82
107
  *
83
108
  * Static configuration, not a callback — see `render.ts`. What a CONTAINER
84
109
  * shows for its items is a `preview`, which is a different thing entirely.
85
110
  */
86
- render(input: StringRender): StringSchema<Src>;
111
+ render(input: FieldRender): StringSchema<Src>;
87
112
  /**
88
113
  * How this VALUE is shown where a preview of it is needed — a row in a
89
114
  * sortable list, a reference dropdown, a search hit. Never how the field