@valbuild/core 0.127.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 CHANGED
@@ -1,5 +1,117 @@
1
1
  # @valbuild/core
2
2
 
3
+ ## 0.128.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [#637](https://github.com/valbuild/val/pull/637) [`8b52b33`](https://github.com/valbuild/val/commit/8b52b33e1f629f14f66cc71dcc7cf415d210c7d4) Thanks [@freekh](https://github.com/freekh)! - **Breaking:** the `directory` option is now `dir`, on every schema that takes
8
+ it — `s.imageset()`, `s.fileset()` and `s.image()`.
9
+
10
+ ```ts
11
+ // before
12
+ s.imageset({ directory: "/public/val/images" });
13
+ s.fileset({ accept: "application/pdf", directory: "/public/val/docs" });
14
+ s.image({ directory: "/public/val/heroes" });
15
+
16
+ // after
17
+ s.imageset({ dir: "/public/val/images" });
18
+ s.fileset({ accept: "application/pdf", dir: "/public/val/docs" });
19
+ s.image({ dir: "/public/val/heroes" });
20
+ ```
21
+
22
+ Same meaning: where uploads for that schema land. Still required on
23
+ `s.imageset()` and `s.fileset()`, still optional on `s.image()`.
24
+
25
+ **`files.directory` in `val.config.ts` is NOT renamed.** That is the
26
+ project-wide files directory, a different option with a different scope, and it
27
+ keeps its name.
28
+
29
+ `dir` is the serialized name too, and two things follow from that. Neither
30
+ needs any action, but both are worth knowing:
31
+
32
+ - **History still reads pre-rename commits.** A commit record stores the schema
33
+ each module was under, so commits from before this release carry `directory`.
34
+ The serialized-schema parser accepts that key and reads it as `dir`, which is
35
+ what keeps a historical gallery from showing up with no directory at all.
36
+ - **Published remote files re-validate once.** The serialized schema is what a
37
+ remote file's validation hash is computed from, so a file uploaded under a
38
+ schema that sets this option gets one re-download and re-check, after which
39
+ the ref is rewritten with the new hash. This is the same self-healing path a
40
+ core version bump already takes.
41
+
42
+ - [#643](https://github.com/valbuild/val/pull/643) [`362fb49`](https://github.com/valbuild/val/commit/362fb49f30d2b04c4ff78d54dca2bf5ad978a05c) Thanks [@freekh](https://github.com/freekh)! - `s.imageset({ alt })` now types an entry's `alt` from the `alt` schema, instead
43
+ of always calling it `string | null`.
44
+
45
+ That single hard-coded type made the locale-record form **unusable** and the
46
+ required form **unsound**, in opposite directions:
47
+
48
+ ```ts
49
+ // Before: documented, but the source could not be typed.
50
+ // Type '{ en: string; no: string; }' is not assignable to type 'string'
51
+ s.imageset({ dir: "/public/val/img", alt: s.record(s.string()) });
52
+
53
+ // Before: this compiled, then failed validation with
54
+ // Expected 'string', got 'null'
55
+ s.imageset({ dir: "/public/val/img", alt: s.string() });
56
+ // → { width, height, mimeType, alt: null }
57
+ ```
58
+
59
+ Both now behave as the schema says: a locale record types as
60
+ `Record<string, string>`, a required alt as `string`, and an omitted or
61
+ `.nullable()` alt as `string | null` exactly as before.
62
+
63
+ `ImagesetEntryMetadata` takes an optional alt parameter and defaults to
64
+ `string | null`, so existing references keep working unchanged. Where a gallery
65
+ of any alt shape is acceptable — `s.image(galleryModule)` is the one that
66
+ matters, since a field carries its own alt and never reads the gallery's — write
67
+ `ImagesetEntryMetadata<AltSource>`.
68
+
69
+ The one thing to know if you had worked around this: an explicit
70
+ `Record<string, ImagesetEntryMetadata>` annotation on the source of a
71
+ **required**-alt gallery is now too wide and will not compile. Name the alt type
72
+ (`ImagesetEntryMetadata<string>`), or drop the annotation and let the schema
73
+ type it.
74
+
75
+ - [#637](https://github.com/valbuild/val/pull/637) [`c595799`](https://github.com/valbuild/val/commit/c59579977a436ec530c6c69f2b340ab9829d97ab) Thanks [@freekh](https://github.com/freekh)! - **Breaking:** `s.images()` is now `s.imageset()`, and `s.files()` is now
76
+ `s.fileset()`.
77
+
78
+ ```ts
79
+ // before
80
+ s.images({ directory: "/public/val/images" });
81
+ s.files({ accept: "application/pdf", directory: "/public/val/docs" });
82
+
83
+ // after
84
+ s.imageset({ dir: "/public/val/images" });
85
+ s.fileset({ accept: "application/pdf", dir: "/public/val/docs" });
86
+ ```
87
+
88
+ Nothing about their behaviour changed — same `.remote()`, same
89
+ record-of-metadata content keyed by file path. (`directory` is renamed to
90
+ `dir` in the same release; see the separate note.)
91
+
92
+ The old names were a trap. `s.images()` and `s.image()` differ by one letter,
93
+ which reads as "the same thing, but several" — while the real difference is that
94
+ `s.imageset()` defines a **whole module** and `s.image()` defines a **field**.
95
+ They are not variants of each other, and the plural spelling suggested they were.
96
+ `-set` names the container instead, so the two now look as different as they are.
97
+
98
+ `s.image(imagesetModule)` and `s.file(filesetModule)` are unchanged: that is
99
+ still how a field picks one entry out of a set.
100
+
101
+ If you are also moving off the `remote` option (removed in the same release),
102
+ the combined migration is:
103
+
104
+ ```ts
105
+ // before
106
+ s.images({ directory: "/public/val/images", remote: true });
107
+
108
+ // after
109
+ s.imageset({ dir: "/public/val/images" }).remote();
110
+ ```
111
+
112
+ The rename is a hard error rather than a silent one: `s.images` no longer
113
+ exists, so an un-migrated call fails immediately in TypeScript and at runtime.
114
+
3
115
  ## 0.127.0
4
116
 
5
117
  ### Minor Changes
@@ -12,7 +12,7 @@ import { literal } from "./schema/literal.js";
12
12
  import { keyOf } from "./schema/keyOf.js";
13
13
  import { record } from "./schema/record.js";
14
14
  import { file } from "./schema/file.js";
15
- import { files } from "./schema/files.js";
15
+ import { fileset } from "./schema/fileset.js";
16
16
  import { date } from "./schema/date.js";
17
17
  import { datetime } from "./schema/datetime.js";
18
18
  import { code } from "./schema/code.js";
@@ -20,7 +20,7 @@ import { color } from "./schema/color.js";
20
20
  import { locale } from "./schema/locale.js";
21
21
  import { route } from "./schema/route.js";
22
22
  import { router } from "./schema/router.js";
23
- import { images } from "./schema/images.js";
23
+ import { imageset } from "./schema/imageset.js";
24
24
  import { settings } from "./schema/settings.js";
25
25
  export type InitSchema = {
26
26
  /**
@@ -148,7 +148,7 @@ export type InitSchema = {
148
148
  *
149
149
  * @example
150
150
  * // Backed by a gallery: width, height and mimeType live there.
151
- * import galleryVal from "./gallery.val"; // an s.images() module
151
+ * import galleryVal from "./gallery.val"; // an s.imageset() module
152
152
  * const schema = s.image(galleryVal);
153
153
  * export default c.define("/example.val.ts", schema, {
154
154
  * path: "/public/val/example.png",
@@ -311,9 +311,9 @@ export type InitSchema = {
311
311
  *
312
312
  * @example
313
313
  * ```typescript
314
- * const schema = s.images({
314
+ * const schema = s.imageset({
315
315
  * accept: "image/webp",
316
- * directory: "/public/val/images",
316
+ * dir: "/public/val/images",
317
317
  * alt: s.string().minLength(4),
318
318
  * });
319
319
  * export default c.define("/content/images.val.ts", schema, {
@@ -326,15 +326,15 @@ export type InitSchema = {
326
326
  * });
327
327
  * ```
328
328
  */
329
- readonly images: typeof images;
329
+ readonly imageset: typeof imageset;
330
330
  /**
331
331
  * Define a collection of files.
332
332
  *
333
333
  * @example
334
334
  * ```typescript
335
- * const schema = s.files({
335
+ * const schema = s.fileset({
336
336
  * accept: "application/pdf",
337
- * directory: "/public/val/documents",
337
+ * dir: "/public/val/documents",
338
338
  * });
339
339
  * export default c.define("/content/documents.val.ts", schema, {
340
340
  * "/public/val/documents/report.pdf": {
@@ -343,7 +343,7 @@ export type InitSchema = {
343
343
  * });
344
344
  * ```
345
345
  */
346
- readonly files: typeof files;
346
+ readonly fileset: typeof fileset;
347
347
  /**
348
348
  * Define the project's settings.
349
349
  *
@@ -399,11 +399,11 @@ export declare function initSchema(): {
399
399
  keyOf: <Src extends import("./selector/index.js").GenericSelector<import("./source/index.js").SourceObject> & import("./module.js").ValModuleBrand>(valModule: Src) => import("./index.js").KeyOfSchema<Src, Src extends import("./selector/index.js").GenericSelector<infer S extends import("./source/index.js").Source, undefined> ? S extends readonly import("./source/index.js").Source[] ? number : S extends import("./source/index.js").SourceObject ? string extends keyof S ? import("./index.js").RawString : keyof S : S extends Record<string, import("./source/index.js").Source> ? import("./index.js").RawString : never : never>;
400
400
  record: typeof record;
401
401
  file: typeof file;
402
- files: (options: import("./schema/files.js").FilesOptions) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
402
+ fileset: (options: import("./schema/fileset.js").FilesetOptions) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
403
403
  mimeType: import("./index.js").StringSchema<string>;
404
404
  }, {
405
405
  mimeType: string;
406
- }>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/files.js").FilesEntryMetadata>>;
406
+ }>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/fileset.js").FilesetEntryMetadata>>;
407
407
  date: (options?: Record<string, never>) => import("./index.js").DateSchema<import("./index.js").RawString>;
408
408
  datetime: (options?: Record<string, never>) => import("./index.js").DateTimeSchema<import("./index.js").RawString>;
409
409
  color: (options?: import("./index.js").ColorOptions) => import("./index.js").ColorSchema<import("./index.js").RawString>;
@@ -411,16 +411,16 @@ export declare function initSchema(): {
411
411
  locale: () => import("./index.js").LocaleSchema<string>;
412
412
  route: <T extends string>(options?: Record<string, never>) => import("./index.js").RouteSchema<T>;
413
413
  router: typeof router;
414
- images: <Accept extends `image/${string}`>(options: import("./schema/images.js").ImagesOptions<Accept>) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
414
+ imageset: <Accept extends `image/${string}`, Alt extends import("./schema/imageset.js").AltSchema = import("./index.js").StringSchema<string | null>>(options: import("./schema/imageset.js").ImagesetOptions<Accept, Alt>) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
415
415
  width: import("./index.js").NumberSchema<number>;
416
416
  height: import("./index.js").NumberSchema<number>;
417
417
  mimeType: import("./index.js").StringSchema<string>;
418
- alt: import("./index.js").StringSchema<string | null>;
418
+ alt: Alt;
419
419
  }, {
420
420
  width: number;
421
421
  height: number;
422
422
  mimeType: string;
423
- alt: string | null;
424
- }>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/images.js").ImagesEntryMetadata>>;
423
+ alt: import("./schema/index.js").SelectorOfSchema<Alt>;
424
+ }>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/imageset.js").ImagesetEntryMetadata<import("./schema/imageset.js").AltSourceOf<Alt>>>>;
425
425
  settings: typeof settings;
426
426
  };
@@ -6,7 +6,7 @@ import { ValidationError, ValidationErrors } from "./validation/ValidationError.
6
6
  import { ValModule } from "../index.js";
7
7
  import { ItemPreviewInput, PreviewItem, ReifiedPreview } from "../preview.js";
8
8
  import { FieldRender } from "../render.js";
9
- import { FilesEntryMetadata } from "./files.js";
9
+ import { FilesetEntryMetadata } from "./fileset.js";
10
10
  export type FileOptions = {
11
11
  accept?: string;
12
12
  };
@@ -36,7 +36,7 @@ export declare class FileSchema<Src extends FileSource | null> extends Schema<Sr
36
36
  private readonly description?;
37
37
  private readonly renderInput;
38
38
  private readonly previewInput;
39
- constructor(options?: FileOptions | undefined, opt?: boolean, isRemote?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], moduleMetadata?: Record<ModulePath, Record<string, FilesEntryMetadata>>, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
39
+ constructor(options?: FileOptions | undefined, opt?: boolean, isRemote?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], moduleMetadata?: Record<ModulePath, Record<string, FilesetEntryMetadata>>, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
40
40
  /**
41
41
  * Describe this field.
42
42
  *
@@ -151,6 +151,6 @@ export declare class FileSchema<Src extends FileSource | null> extends Schema<Sr
151
151
  * A file picked from a gallery. Its mime type lives in the gallery, so the
152
152
  * field carries only the path.
153
153
  */
154
- export declare function file(galleryModule: ValModule<Record<string, FilesEntryMetadata>>): FileSchema<GalleryFileSource>;
154
+ export declare function file(galleryModule: ValModule<Record<string, FilesetEntryMetadata>>): FileSchema<GalleryFileSource>;
155
155
  /** A file of its own, carrying its own mime type. */
156
156
  export declare function file(options?: FileOptions): FileSchema<FileSource>;
@@ -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.files()
7
+ * Options for s.fileset()
8
8
  */
9
- export type FilesOptions = {
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
- directory: "/public" | `/public/${string}`;
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 FilesEntryMetadata = {
28
+ export type FilesetEntryMetadata = {
29
29
  mimeType: string;
30
30
  };
31
- export type SerializedFilesSchema = SerializedRecordSchema;
32
- type FilesItemProps = {
31
+ export type SerializedFilesetSchema = SerializedRecordSchema;
32
+ type FilesetItemProps = {
33
33
  mimeType: StringSchema<string>;
34
34
  };
35
- type FilesItemSrc = {
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.files({
45
+ * const schema = s.fileset({
46
46
  * accept: "application/pdf",
47
- * directory: "/public/val/documents",
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 files: (options: FilesOptions) => RecordSchema<ObjectSchema<FilesItemProps, FilesItemSrc>, Schema<string>, Record<string, FilesEntryMetadata>>;
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.images({ ... }).remote()`: the Studio's version
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.images()` /
47
- * `s.files()`) serializes as a record of metadata with no image schema inside
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 { ImagesEntryMetadata } from "./images.js";
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
- directory?: string;
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, ImagesEntryMetadata>>, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
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", directory: "/public/val/images" })
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, ImagesEntryMetadata>>, galleryOptions?: GalleryImageOptions): ImageSchema<GalleryImageSource>;
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
- * Options for s.images()
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
- export type ImagesOptions<Accept extends `image/${string}`> = {
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
- directory: "/public" | `/public/${string}`;
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 ImagesEntryMetadata = {
82
+ export type ImagesetEntryMetadata<Alt extends AltSource = string | null> = {
50
83
  width: number;
51
84
  height: number;
52
85
  mimeType: string;
53
- alt: string | null;
86
+ alt: Alt;
54
87
  hotspot?: {
55
88
  x: number;
56
89
  y: number;
57
90
  };
58
91
  };
59
- export type SerializedImagesSchema = SerializedRecordSchema;
60
- type ImagesItemProps = {
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: StringSchema<string | null>;
97
+ alt: Alt;
65
98
  };
66
- type ImagesItemSrc = {
99
+ type ImagesetItemSrc<Alt extends AltSchema> = {
67
100
  width: number;
68
101
  height: number;
69
102
  mimeType: string;
70
- alt: string | null;
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.images({
114
+ * const schema = s.imageset({
82
115
  * accept: "image/webp",
83
- * directory: "/public/val/images",
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 images: <Accept extends `image/${string}`>(options: ImagesOptions<Accept>) => RecordSchema<ObjectSchema<ImagesItemProps, ImagesItemSrc>, Schema<string>, Record<string, ImagesEntryMetadata>>;
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 {};
@@ -11,7 +11,7 @@ import type { ImageEncodeOption } from "./image.js";
11
11
  type MediaOptions = {
12
12
  type: "files" | "images";
13
13
  accept: string;
14
- directory: string;
14
+ dir: string;
15
15
  remote: boolean;
16
16
  altSchema?: Schema<SelectorSource>;
17
17
  /** Images only: how uploads are re-encoded in the browser. See `image.ts`. */
@@ -34,7 +34,7 @@ export type SerializedRecordSchema = {
34
34
  customValidate?: boolean;
35
35
  mediaType?: "files" | "images";
36
36
  accept?: string;
37
- directory?: string;
37
+ dir?: string;
38
38
  remote?: boolean;
39
39
  encode?: ImageEncodeOption;
40
40
  alt?: SerializedSchema;
@@ -229,7 +229,7 @@ RO extends boolean = false> extends Schema<Src> {
229
229
  * Allow the files in this gallery to be stored on Val's remote content host
230
230
  * instead of in your repository.
231
231
  *
232
- * For `s.images()` and `s.files()`, which are records of media. Remote is off
232
+ * For `s.imageset()` and `s.fileset()`, which are records of media. Remote is off
233
233
  * until this is called.
234
234
  *
235
235
  * Each entry is then keyed by its URL on the content host rather than by a
@@ -238,7 +238,7 @@ RO extends boolean = false> extends Schema<Src> {
238
238
  * upload it and rewrite the key.
239
239
  *
240
240
  * @example
241
- * const schema = s.images({ directory: "/public/val/images" }).remote();
241
+ * const schema = s.imageset({ dir: "/public/val/images" }).remote();
242
242
  * export default c.define("/content/images.val.ts", schema, {
243
243
  * "https://remote.val.build/file/p/my-project/b/01/v/1.0.0/h/8f2a1c/f/3b9d70/p/public/val/images/hero.webp":
244
244
  * {
@@ -257,7 +257,7 @@ RO extends boolean = false> extends Schema<Src> {
257
257
  * which lets the runtime, the Studio and validation work one entry at a time
258
258
  * so a record/router can scale to many thousands of entries.
259
259
  *
260
- * Not supported on image/file galleries (`s.images()` / `s.files()`).
260
+ * Not supported on image/file galleries (`s.imageset()` / `s.fileset()`).
261
261
  *
262
262
  * Only supported on a module's ROOT record/router — a `.jsonValues()` record
263
263
  * nested inside an object/array/record is rejected at startup with a module
@@ -284,7 +284,7 @@ RO extends boolean = false> extends Schema<Src> {
284
284
  * bucket — instead of in the module.
285
285
  *
286
286
  * Available on every record-derived schema, which is `s.record()`,
287
- * `s.router()`, `s.images()` and `s.files()` alike: a router is a
287
+ * `s.router()`, `s.imageset()` and `s.fileset()` alike: a router is a
288
288
  * `RecordSchema` with a `ValRouter`, a gallery one with media options, so all
289
289
  * four get external storage from this one method.
290
290
  *