@valbuild/core 0.144.0 → 0.146.2

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 CHANGED
@@ -1,5 +1,37 @@
1
1
  # @valbuild/core
2
2
 
3
+ ## 0.146.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#871](https://github.com/valbuild/val/pull/871) [`e4913e1`](https://github.com/valbuild/val/commit/e4913e1f1178e09987a7c03fd540909ab4b499b0) Thanks [@freekh](https://github.com/freekh)! - An image, file or video picked from a gallery is checked against the gallery as it is now. Picking one uploaded since the site was built (still unpublished, saved in a proposal, or published without a rebuild) used to fail with "The gallery does not have an image at …" and refuse the save. In a proposal, Save now reads "Fix N" and goes to the validation errors instead of being refused by the server, as Publish does on the site.
8
+
9
+ ## 0.145.0
10
+
11
+ ### Minor Changes
12
+
13
+ - [#851](https://github.com/valbuild/val/pull/851) [`b6cb939`](https://github.com/valbuild/val/commit/b6cb9395d8bdcd5182e9c6ba84f5284930de2fa5) Thanks [@freekh](https://github.com/freekh)! - New `s.int()` for whole numbers, `.step()` on `s.int()` and `s.number()`, and a slider for both:
14
+
15
+ ```ts
16
+ s.object({
17
+ columns: s.int().min(1).max(6),
18
+ opacity: s
19
+ .int()
20
+ .min(0)
21
+ .max(100)
22
+ .step(5)
23
+ .render({ as: "slider", min: 0, max: 100 }),
24
+ lineHeight: s.number().render({ as: "slider", min: 1, max: 2, step: 0.1 }),
25
+ });
26
+ ```
27
+
28
+ - `.min()`, `.max()` and `.step()` are rules the validator checks. A step is counted from `min`, or from 0 when there is none.
29
+ - `.render({ as: "slider", min, max, step? })` is how the field is edited: a slider, with a box beside it for typing an exact value. `min` and `max` are required. The slider's step defaults to the schema's `.step()`, then to 1.
30
+ - The slider can only produce values the schema accepts. A slider outside `.min()`/`.max()`, with a step that is not a multiple of `.step()`, or with decimals on an `s.int()` throws where the schema is defined.
31
+ - A value outside the slider's range is still valid content. The field says it is out of the slider's reach and offers "Set to" the nearest stop. A value that breaks a rule offers "Round to" the nearest allowed value.
32
+ - Fixed: `.min(0)` and `.max(0)` on `s.number()` were ignored.
33
+ - A new item of a number schema starts at the nearest allowed value instead of `0`, so `s.int().min(1)` starts at 1.
34
+
3
35
  ## 0.144.0
4
36
 
5
37
  ### Minor Changes
@@ -51,13 +51,15 @@ import { getValidationBasis, getValidationHash } from "./remote/validationBasis.
51
51
  import { getFileHash, hashToRemoteFileHash } from "./remote/fileHash.js";
52
52
  import { splitRemoteRef } from "./remote/splitRemoteRef.js";
53
53
  import { galleriesOf } from "./source/galleries.js";
54
- import { fillFromGallery, galleryEntryOf, galleryKeyOf, isHlsVideo, isRemoteMediaPath, mediaUrl, resolveMedia, resolveVideo } from "./source/media.js";
54
+ import { fillFromGallery, galleryEntryOf, galleryKeyOf, galleryMarkerOf, isHlsVideo, isRemoteMediaPath, mediaUrl, resolveMedia, resolveVideo } from "./source/media.js";
55
55
  import { colorToHex, convertColor, detectColorFormat, formatColor, parseColor } from "./schema/colorFormat.js";
56
56
  export { type SerializedArraySchema, ArraySchema } from "./schema/array.js";
57
57
  export { type SerializedObjectSchema, ObjectSchema } from "./schema/object.js";
58
58
  export { type SerializedRecordSchema, type MediaCollectionType, RecordSchema, } from "./schema/record.js";
59
59
  export { type SerializedStringSchema, StringSchema } from "./schema/string.js";
60
60
  export { type SerializedNumberSchema, NumberSchema } from "./schema/number.js";
61
+ import { nearestAllowedNumber, isOnStep, stepOrigin } from "./schema/numberRules.js";
62
+ export type { NumberRules } from "./schema/numberRules.js";
61
63
  export { type SerializedBooleanSchema, BooleanSchema } from "./schema/boolean.js";
62
64
  export { type SerializedImageSchema, ImageSchema } from "./schema/image.js";
63
65
  export { type SerializedFileSchema, FileSchema } from "./schema/file.js";
@@ -89,7 +91,7 @@ export { type SerializedUnionSchema, type UnionSchema, type SerializedStringUnio
89
91
  export { type SerializedLiteralSchema, LiteralSchema } from "./schema/literal.js";
90
92
  export { deserializeSchema } from "./schema/deserialize.js";
91
93
  export { type PreviewItem, type ItemPreviewInput, type RecordPreview, type ArrayPreview, type ReifiedPreview, type PreviewScope, previewScope, } from "./preview.js";
92
- export { type InlineRender, type FieldRender, isInlineRender } from "./render.js";
94
+ export { type InlineRender, type FieldRender, type SliderRender, type NumberRender, isInlineRender, } from "./render.js";
93
95
  export type { ValRouter, ExternalPageRouter, RouteValidationError, } from "./router.js";
94
96
  export { parseNextJsRoutePattern, parseTanStackRoutePattern, tanStackSegmentsOfRoutePath, validateUrlAgainstPattern, } from "./router.js";
95
97
  export { getSourcePathFromRoute, isPageRouter } from "./getSourcePathFromRoute.js";
@@ -153,6 +155,7 @@ declare const Internal: {
153
155
  galleriesOf: typeof galleriesOf;
154
156
  galleryEntryOf: typeof galleryEntryOf;
155
157
  galleryKeyOf: typeof galleryKeyOf;
158
+ galleryMarkerOf: typeof galleryMarkerOf;
156
159
  GALLERY_DEFAULT_GROUPS: readonly (readonly string[])[];
157
160
  isHlsVideo: typeof isHlsVideo;
158
161
  HLS_MIME_TYPE: string;
@@ -173,6 +176,11 @@ declare const Internal: {
173
176
  rejectScheme: typeof rejectScheme;
174
177
  schemeOf: typeof schemeOf;
175
178
  UNSAFE_URL_SCHEMES: readonly ["javascript", "data", "vbscript", "file", "blob"];
179
+ number: {
180
+ nearestAllowedNumber: typeof nearestAllowedNumber;
181
+ isOnStep: typeof isOnStep;
182
+ stepOrigin: typeof stepOrigin;
183
+ };
176
184
  color: {
177
185
  parseColor: typeof parseColor;
178
186
  formatColor: typeof formatColor;
@@ -1,5 +1,5 @@
1
1
  import { array } from "./schema/array.js";
2
- import { number } from "./schema/number.js";
2
+ import { int, number } from "./schema/number.js";
3
3
  import { object } from "./schema/object.js";
4
4
  import { string } from "./schema/string.js";
5
5
  import { boolean } from "./schema/boolean.js";
@@ -74,6 +74,28 @@ export type InitSchema = {
74
74
  *
75
75
  */
76
76
  readonly number: typeof number;
77
+ /**
78
+ * Define a whole number. The source is an ordinary `number`; `s.int()` adds
79
+ * the rule that it has no decimals.
80
+ *
81
+ * Takes `.min()`, `.max()` and `.step()` for what the value may be, and
82
+ * `.render({ as: "slider", min, max })` to edit it with a slider.
83
+ *
84
+ * @example
85
+ * const schema = s.object({
86
+ * columns: s.int().min(1).max(6),
87
+ * opacity: s.int().min(0).max(100).step(5).render({
88
+ * as: "slider",
89
+ * min: 0,
90
+ * max: 100,
91
+ * }),
92
+ * });
93
+ * export default c.define("/example.val.ts", schema, {
94
+ * columns: 3,
95
+ * opacity: 40,
96
+ * });
97
+ */
98
+ readonly int: typeof int;
77
99
  /**
78
100
  * Define one of several object shapes, told apart by a tag field.
79
101
  *
@@ -27,11 +27,12 @@ import type { SerializedSchema } from "./schema/index.js";
27
27
  */
28
28
  /**
29
29
  * A RENDER is how a field is laid out in the editor, and it applies only when
30
- * you are looking at the field. It is declared by a CONTAINER about its
31
- * children, never by a child about itself: `s.array(item).render({ as: "inline" })`
32
- * and `s.record(item).render({ as: "inline" })`. The one other schema that takes
33
- * a render is `s.keyOf(...)`, where it means something of its own (see
34
- * `KeyOfSchema.render`).
30
+ * you are looking at the field. On a list it is declared by the CONTAINER about
31
+ * its children, never by a child about itself: `s.array(item).render({ as: "inline" })`
32
+ * and `s.record(item).render({ as: "inline" })`. Two other schemas take a
33
+ * render, each meaning something of its own: `s.keyOf(...)` (see
34
+ * `KeyOfSchema.render`) and `s.number()` / `s.int()`, whose `{ as: "slider" }`
35
+ * is a different input for the same value (see {@link SliderRender}).
35
36
  *
36
37
  * Why the container: inline is a property of the LIST — "my items are edited in
37
38
  * their rows" — and the item schema is the wrong place to say it. An item can be
@@ -89,6 +90,38 @@ export type InlineRender = {
89
90
  * What `.render(...)` takes, and what the serialized schema carries verbatim.
90
91
  */
91
92
  export type FieldRender = InlineRender;
93
+ /**
94
+ * `{ as: "slider", min, max }` on `s.int()` or `s.number()`: the value is
95
+ * edited with a slider from `min` to `max`, next to a box that takes a typed
96
+ * value.
97
+ *
98
+ * `min` and `max` are REQUIRED, because a slider without ends is not a slider.
99
+ * They are the slider's own and are not validation: a value outside them is
100
+ * still valid content (if the schema allows it), and the field says it is out
101
+ * of the slider's reach and offers to bring it back. What the schema accepts is
102
+ * `.min()`, `.max()` and `.step()` on the schema itself.
103
+ *
104
+ * The one rule tying the two together: **the slider may only produce values
105
+ * the schema accepts.** So its range has to lie inside the schema's, its step
106
+ * has to be a multiple of the schema's step and its `min` on the schema's grid,
107
+ * and on `s.int()` all of them have to be whole numbers. A schema that breaks
108
+ * that throws where it is defined — see `NumberSchema`.
109
+ *
110
+ * `step` defaults to the schema's `.step()`, and to 1 when there is none.
111
+ *
112
+ * Unlike `inline` this is about the field itself — no list reads it, and
113
+ * {@link isInlineRender} answers `false` for it.
114
+ */
115
+ export type SliderRender = {
116
+ as: "slider";
117
+ min: number;
118
+ max: number;
119
+ step?: number;
120
+ };
121
+ /**
122
+ * What `s.number().render(...)` and `s.int().render(...)` take.
123
+ */
124
+ export type NumberRender = SliderRender;
92
125
  /**
93
126
  * Does this CONTAINER edit its items inside their list rows, rather than behind
94
127
  * clickable preview rows that navigate to them?
@@ -167,6 +167,8 @@ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<
167
167
  * alt — it carries its own — so it only needs the entry keys and the
168
168
  * dimensions, and a gallery of any alt shape can back it.
169
169
  */
170
+ /** The module path of the gallery this field points at. */
171
+ private galleryModulePath;
170
172
  private galleryEntries;
171
173
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
172
174
  nullable(): ImageSchema<Src | null>;
@@ -1,16 +1,23 @@
1
1
  import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
2
2
  import { ItemPreviewInput, PreviewItem } from "../preview.js";
3
+ import { NumberRender } from "../render.js";
3
4
  import { SourcePath } from "../val/index.js";
4
5
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
5
- type NumberOptions = {
6
- max?: number;
7
- min?: number;
8
- };
6
+ import { NumberRules } from "./numberRules.js";
7
+ /**
8
+ * What the value has to be. `integer` is what makes a number schema an
9
+ * `s.int()`: the source is a number either way, so an int is a number schema
10
+ * with one more rule rather than a schema type of its own (which every walk
11
+ * over a schema would have had to learn).
12
+ */
13
+ type NumberOptions = NumberRules;
9
14
  export type SerializedNumberSchema = {
10
15
  type: "number";
11
16
  /** Set when this schema declares a `preview`. The closure itself cannot serialize. */
12
17
  preview?: true;
13
18
  options?: NumberOptions;
19
+ /** How the field is edited. Static config — see `render.ts`. */
20
+ render?: NumberRender;
14
21
  opt: boolean;
15
22
  customValidate?: boolean;
16
23
  readonly?: boolean;
@@ -25,7 +32,8 @@ export declare class NumberSchema<Src extends number | null> extends Schema<Src>
25
32
  private readonly isHidden;
26
33
  private readonly description?;
27
34
  private readonly previewInput;
28
- constructor(options?: NumberOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, previewInput?: ItemPreviewInput<Src> | null);
35
+ private readonly renderInput;
36
+ constructor(options?: NumberOptions | undefined, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, previewInput?: ItemPreviewInput<Src> | null, renderInput?: NumberRender | null);
29
37
  /**
30
38
  * Describe this field.
31
39
  *
@@ -86,6 +94,37 @@ export declare class NumberSchema<Src extends number | null> extends Schema<Src>
86
94
  * export default c.define("/example.val.ts", schema, 3);
87
95
  */
88
96
  min(min: number): NumberSchema<Src>;
97
+ /**
98
+ * Validate that the number is on a grid of `step`: one of `min`, `min + step`,
99
+ * `min + 2 * step` and so on, counted from 0 when there is no `.min()`.
100
+ *
101
+ * A slider rendered on this field moves in the same steps unless its render
102
+ * says otherwise (and then its step has to be a multiple of this one).
103
+ *
104
+ * @example
105
+ * const schema = s.int().min(0).max(100).step(5);
106
+ * export default c.define("/example.val.ts", schema, 40);
107
+ */
108
+ step(step: number): NumberSchema<Src>;
109
+ /**
110
+ * Choose how this field is edited. `{ as: "slider", min, max }` edits it with
111
+ * a slider from `min` to `max`, next to a box for typing an exact value.
112
+ *
113
+ * `min` and `max` are required — a slider without ends is not a slider — and
114
+ * they are the slider's, not validation: use `.min()` / `.max()` for what the
115
+ * value may be. The slider can only produce values this schema accepts, so
116
+ * its range must lie inside `.min()`/`.max()`, its `step` (which defaults to
117
+ * `.step()`, then 1) must be a multiple of `.step()`, and on `s.int()` all of
118
+ * them must be whole numbers. A schema that breaks this throws here.
119
+ *
120
+ * Static configuration, not a callback — see `render.ts`. A second
121
+ * `.render(...)` replaces the first.
122
+ *
123
+ * @example
124
+ * const schema = s.int().render({ as: "slider", min: 0, max: 100, step: 5 });
125
+ * export default c.define("/example.val.ts", schema, 40);
126
+ */
127
+ render(input: NumberRender): NumberSchema<Src>;
89
128
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
90
129
  /**
91
130
  * How this VALUE is shown where a preview of it is needed — a row in a
@@ -104,4 +143,5 @@ export declare class NumberSchema<Src extends number | null> extends Schema<Src>
104
143
  protected executeSerialize(): SerializedSchema;
105
144
  }
106
145
  export declare const number: (options?: NumberOptions) => NumberSchema<number>;
146
+ export declare const int: () => NumberSchema<number>;
107
147
  export {};
@@ -0,0 +1,27 @@
1
+ /**
2
+ * What a number schema accepts, in one place, so the validator and the
3
+ * Studio's "Round to …" / "Set to …" buttons cannot disagree about it.
4
+ *
5
+ * A button that offers a value the validator then rejects is worse than no
6
+ * button, which is exactly what two copies of this arithmetic would drift into.
7
+ */
8
+ export type NumberRules = {
9
+ min?: number;
10
+ max?: number;
11
+ /** Whole numbers only — `s.int()`. */
12
+ integer?: boolean;
13
+ /** Allowed values are `origin + n * step`, where the origin is `min` or 0. */
14
+ step?: number;
15
+ };
16
+ /** Where the step grid starts: the schema's `min`, or 0 when it has none. */
17
+ export declare function stepOrigin(rules: NumberRules): number;
18
+ /** Is `value` one of `origin + n * step`? */
19
+ export declare function isOnStep(value: number, origin: number, step: number): boolean;
20
+ /**
21
+ * The allowed value closest to `value`: inside `min`/`max`, on the step grid,
22
+ * and whole when the schema is an int.
23
+ *
24
+ * Returns `null` when the rules leave nothing to snap to — a `min` above
25
+ * `max`, or a range too narrow to hold a single step or a single whole number.
26
+ */
27
+ export declare function nearestAllowedNumber(value: number, rules: NumberRules): number | null;
@@ -1,2 +1,2 @@
1
- export declare const ValidationFix: readonly ["image:add-metadata", "image:check-metadata", "image:upload-remote", "image:download-remote", "image:check-remote", "images:check-remote", "images:upload-remote", "file:add-metadata", "file:check-metadata", "file:upload-remote", "file:download-remote", "file:check-remote", "files:check-remote", "files:upload-remote", "video:add-metadata", "video:upload-remote", "video:download-remote", "videos:add-metadata", "videos:check-remote", "videos:upload-remote", "keyof:check-keys", "router:check-route", "locale:check-locale", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "videos:check-unique-folder", "videos:check-all-files", "jsonValues:extract-entry", "record:fill-keys", "external:upload", "view:check-module"];
1
+ export declare const ValidationFix: readonly ["image:add-metadata", "image:check-metadata", "image:upload-remote", "image:download-remote", "image:check-remote", "images:check-remote", "images:upload-remote", "file:add-metadata", "file:check-metadata", "file:upload-remote", "file:download-remote", "file:check-remote", "files:check-remote", "files:upload-remote", "video:add-metadata", "video:upload-remote", "video:download-remote", "videos:add-metadata", "videos:check-remote", "videos:upload-remote", "keyof:check-keys", "image:check-gallery", "file:check-gallery", "video:check-gallery", "router:check-route", "locale:check-locale", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "videos:check-unique-folder", "videos:check-all-files", "jsonValues:extract-entry", "record:fill-keys", "external:upload", "view:check-module"];
2
2
  export type ValidationFix = (typeof ValidationFix)[number];
@@ -10,6 +10,7 @@
10
10
  * a field can move between local and remote without a type error. Remote-ness
11
11
  * is read off the path: anything not under `/public` is remote.
12
12
  */
13
+ import type { ValidationError } from "../schema/validation/ValidationError.js";
13
14
  /** What must stay in frame when a page crops an image. */
14
15
  export type MediaHotspot = {
15
16
  x: number;
@@ -232,6 +233,27 @@ export declare function resolveVideo<S extends GalleryVideoSource>(src: S, video
232
233
  * without (how `splitRemoteRef` returns it).
233
234
  */
234
235
  export declare function galleryKeyOf(path: string, isKey: (key: string) => boolean): string | null;
236
+ /**
237
+ * A gallery-backed field whose path the gallery, as the schema holds it, does
238
+ * not have: the `image:check-gallery` (`file:`, `video:`) marker.
239
+ *
240
+ * The schema holds the gallery as it was EVALUATED -- in a built site, the
241
+ * gallery as it was built. An entry added since (a pending change, a save in
242
+ * a proposal, a content-only publish that never rebuilt the site) is only in
243
+ * the gallery's current Source, so whether the field is wrong is a question
244
+ * for a project-wide snapshot (`resolveSchemaSourceFixes`), as a keyOf's key
245
+ * is. The message is the answer when the gallery does not have it either, so
246
+ * a consumer that does not resolve markers reports what it always did.
247
+ */
248
+ export declare function galleryMarker(media: "image" | "file" | "video", source: {
249
+ path: string;
250
+ }, galleryModulePath: string): ValidationError;
251
+ /** A {@link galleryMarker}'s fields, or `null` for any other error. */
252
+ export declare function galleryMarkerOf(error: ValidationError): {
253
+ path: string;
254
+ gallery: string;
255
+ source: unknown;
256
+ } | null;
235
257
  /**
236
258
  * What a gallery entry holds as a DEFAULT for the fields picked from it, in
237
259
  * the groups a field overrides together.