@valbuild/core 0.101.0 → 0.103.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.
Files changed (42) hide show
  1. package/dist/declarations/src/extractValModules.d.ts +1 -1
  2. package/dist/declarations/src/index.d.ts +13 -11
  3. package/dist/declarations/src/initSchema.d.ts +28 -15
  4. package/dist/declarations/src/initVal.d.ts +0 -6
  5. package/dist/declarations/src/module.d.ts +1 -4
  6. package/dist/declarations/src/render.d.ts +58 -9
  7. package/dist/declarations/src/schema/array.d.ts +13 -3
  8. package/dist/declarations/src/schema/file.d.ts +17 -17
  9. package/dist/declarations/src/schema/files.d.ts +6 -2
  10. package/dist/declarations/src/schema/hasRemoteFileSchema.d.ts +34 -0
  11. package/dist/declarations/src/schema/image.d.ts +15 -6
  12. package/dist/declarations/src/schema/images.d.ts +10 -5
  13. package/dist/declarations/src/schema/index.d.ts +10 -3
  14. package/dist/declarations/src/schema/object.d.ts +2 -2
  15. package/dist/declarations/src/schema/record.d.ts +7 -7
  16. package/dist/declarations/src/schema/remote.d.ts +0 -10
  17. package/dist/declarations/src/schema/string.d.ts +4 -2
  18. package/dist/declarations/src/schema/union.d.ts +2 -2
  19. package/dist/declarations/src/selector/SelectorProxy.d.ts +9 -2
  20. package/dist/declarations/src/selector/index.d.ts +3 -7
  21. package/dist/declarations/src/source/file.d.ts +6 -24
  22. package/dist/declarations/src/source/index.d.ts +3 -7
  23. package/dist/declarations/src/source/json.d.ts +1 -1
  24. package/dist/declarations/src/source/media.d.ts +98 -0
  25. package/dist/declarations/src/source/remote.d.ts +0 -14
  26. package/dist/declarations/src/source/richtext.d.ts +5 -7
  27. package/dist/declarations/src/val/index.d.ts +2 -5
  28. package/dist/{index-a18c24c2.esm.js → index-267a8e9c.esm.js} +1086 -813
  29. package/dist/{index-3e2d700e.cjs.dev.js → index-2e40f2ae.cjs.dev.js} +1086 -813
  30. package/dist/{index-049780fa.cjs.prod.js → index-3572060f.cjs.prod.js} +1086 -813
  31. package/dist/{result-4bd63123.esm.js → result-daff1cae.esm.js} +1 -1
  32. package/dist/valbuild-core.cjs.dev.js +3 -3
  33. package/dist/valbuild-core.cjs.prod.js +3 -3
  34. package/dist/valbuild-core.esm.js +2 -2
  35. package/fp/dist/valbuild-core-fp.esm.js +1 -1
  36. package/package.json +1 -1
  37. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  38. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  39. package/patch/dist/valbuild-core-patch.esm.js +3 -3
  40. package/dist/declarations/src/selector/file.d.ts +0 -10
  41. package/dist/declarations/src/selector/image.d.ts +0 -3
  42. package/dist/declarations/src/source/image.d.ts +0 -19
@@ -21,7 +21,7 @@ export type ExtractedValModules = {
21
21
  * Extracts schemas and sources from a ValModules registry and computes the
22
22
  * deterministic SHAs that the server and client both use to detect changes.
23
23
  *
24
- * Used by ValOps on the server and by ValSyncEngine on the client so the
24
+ * Used by ValOps on the server and by `HostStore` on the client so the
25
25
  * hash algorithm has a single source of truth.
26
26
  */
27
27
  export declare function extractValModules(valModules: ValModules): Promise<ExtractedValModules>;
@@ -3,22 +3,23 @@ export { modules, type ValModules } from "./modules.js";
3
3
  export { extractValModules, type ExtractedValModules, type ExtractedModuleError, } from "./extractValModules.js";
4
4
  export type { InitVal, ValConfig, ValConstructor, ContentConstructor, } from "./initVal.js";
5
5
  export { Schema, type SerializedSchema, type SelectorOfSchema } from "./schema/index.js";
6
+ export { hasRemoteFileSchema } from "./schema/hasRemoteFileSchema.js";
6
7
  export type { ImageMetadata } from "./schema/image.js";
7
8
  export type { FileMetadata } from "./schema/file.js";
8
9
  export type { ValModule, SerializedModule, InferValModuleType } from "./module.js";
9
10
  export type { SourceObject, SourcePrimitive, Source } from "./source/index.js";
10
- export type { FileSource } from "./source/file.js";
11
+ export type { FileSource } from "./source/media.js";
11
12
  export type { JsonSource, JsonOf, JsonImportThunk } from "./source/json.js";
12
- export type { RemoteSource, RemoteRef } from "./source/remote.js";
13
+ export type { RemoteRef } from "./source/remote.js";
13
14
  export { DEFAULT_VAL_REMOTE_HOST } from "./schema/remote.js";
14
15
  export type { RawString } from "./schema/string.js";
15
- export type { ImageSource } from "./source/image.js";
16
+ export type { ImageSource } from "./source/media.js";
17
+ export type { MediaHotspot, MediaSource, GalleryImageSource, GalleryFileSource, } from "./source/media.js";
16
18
  export type { AllRichTextOptions, Bold, Styles, HeadingNode, ImageNode, Italic, LineThrough, ListItemNode, LinkNode, OrderedListNode, ParagraphNode, BrNode, RichTextNode, RichTextOptions, SerializedRichTextOptions, RichTextSource, BlockNode, SpanNode, UnorderedListNode, } from "./source/richtext.js";
17
19
  export { type Val, type SerializedVal, type ModuleFilePath, type PatchId, type ModulePath, type SourcePath, type JsonOfSource, type ParentPatchId, } from "./val/index.js";
18
20
  export type { Json, JsonPrimitive, JsonArray, JsonObject } from "./Json.js";
19
21
  export type { ValidationError, ValidationErrors, } from "./schema/validation/ValidationError.js";
20
22
  export type { ValidationFix } from "./schema/validation/ValidationFix.js";
21
- export { FILE_REF_PROP, FILE_REF_SUBTYPE_TAG } from "./source/file.js";
22
23
  export { VAL_EXTENSION, type SourceArray } from "./source/index.js";
23
24
  export { derefPatch } from "./patch/deref.js";
24
25
  export { type SelectorSource, type SelectorOf, GenericSelector, } from "./selector/index.js";
@@ -27,20 +28,18 @@ declare const ModuleFilePathSep = "?p=";
27
28
  export { ModuleFilePathSep };
28
29
  import { SelectorSource, getSchema } from "./selector/index.js";
29
30
  import { ModulePath, SourcePath, getValPath, isVal } from "./val/index.js";
30
- import { convertFileSource } from "./schema/file.js";
31
31
  import { createValPathOfItem } from "./selector/SelectorProxy.js";
32
32
  import { Operation } from "./patch/index.js";
33
33
  import { initSchema } from "./initSchema.js";
34
34
  import { getMimeType, mimeTypeToFileExt, filenameToMimeType } from "./mimeType/index.js";
35
35
  import { type ImageMetadata } from "./schema/image.js";
36
36
  import { type FileMetadata } from "./schema/file.js";
37
- import { isFile } from "./source/file.js";
38
37
  import { isJson, getJsonImport, resolveJsonValues } from "./source/json.js";
39
38
  import { createRemoteRef } from "./source/remote.js";
40
39
  import { getValidationBasis, getValidationHash } from "./remote/validationBasis.js";
41
40
  import { getFileHash, hashToRemoteFileHash } from "./remote/fileHash.js";
42
41
  import { splitRemoteRef } from "./remote/splitRemoteRef.js";
43
- import { convertRemoteSource } from "./schema/remote.js";
42
+ import { fillFromGallery, isRemoteMediaPath, mediaUrl, resolveMedia } from "./source/media.js";
44
43
  import { colorToHex, convertColor, detectColorFormat, formatColor, parseColor } from "./schema/colorFormat.js";
45
44
  export { type SerializedArraySchema, ArraySchema } from "./schema/array.js";
46
45
  export { type SerializedObjectSchema, ObjectSchema } from "./schema/object.js";
@@ -60,7 +59,7 @@ export { type SerializedRichTextSchema, RichTextSchema, } from "./schema/richtex
60
59
  export { type SerializedUnionSchema, UnionSchema, type SerializedStringUnionSchema, type SerializedObjectUnionSchema, } from "./schema/union.js";
61
60
  export { type SerializedLiteralSchema, LiteralSchema } from "./schema/literal.js";
62
61
  export { deserializeSchema } from "./schema/deserialize.js";
63
- export { type ListRecordRender, type ListArrayRender, type ReifiedRender, type CodeLanguage, type CodeRender, } from "./render.js";
62
+ export { type ListRecordRender, type ListArrayRender, type ReifiedRender, type RenderScope, type CodeLanguage, type CodeRender, renderScope, } from "./render.js";
64
63
  export type { ValRouter, RouteValidationError } from "./router.js";
65
64
  export { getSourcePathFromRoute } from "./getSourcePathFromRoute.js";
66
65
  export declare const FATAL_ERROR_TYPES: readonly ["no-schema", "no-source", "invalid-id", "no-module", "invalid-patch"];
@@ -71,8 +70,12 @@ declare const Internal: {
71
70
  VERSION: {
72
71
  core: string | null;
73
72
  };
74
- convertFileSource: typeof convertFileSource;
75
- convertRemoteSource: typeof convertRemoteSource;
73
+ mediaUrl: typeof mediaUrl;
74
+ resolveMedia: typeof resolveMedia;
75
+ isRemoteMediaPath: typeof isRemoteMediaPath;
76
+ media: {
77
+ fillFromGallery: typeof fillFromGallery;
78
+ };
76
79
  getSchema: typeof getSchema;
77
80
  getValPath: typeof getValPath;
78
81
  getSource: typeof getSource;
@@ -99,7 +102,6 @@ declare const Internal: {
99
102
  };
100
103
  validate: (val: ValModule<SelectorSource>, path: SourcePath, src: unknown) => import("./schema/validation/ValidationError.js").ValidationErrors | undefined;
101
104
  isVal: typeof isVal;
102
- isFile: typeof isFile;
103
105
  isJson: typeof isJson;
104
106
  getJsonImport: typeof getJsonImport;
105
107
  resolveJsonValues: typeof resolveJsonValues;
@@ -99,19 +99,28 @@ export type InitSchema = {
99
99
  /**
100
100
  * Define an image.
101
101
  *
102
- * Use c.image to create an image source.
102
+ * An image is an object with a `path`. `width`, `height` and `mimeType` are
103
+ * read from the file — the VS Code extension fills them in as you type the
104
+ * path, and `npx val validate --fix` adds any that are missing. `alt` and
105
+ * `hotspot` are yours to set.
103
106
  *
104
107
  * @example
105
108
  * const schema = s.image();
106
- * export default c.define("/example.val.ts", schema, c.image("/public/val/example.png", {
107
- * width: 100,
108
- * height: 100,
109
- * mimeType: "image/png",
110
- * hotspot: {
111
- * x: 0.5,
112
- * y: 0.5
113
- * }
114
- * }));
109
+ * export default c.define("/example.val.ts", schema, {
110
+ * path: "/public/val/example.png",
111
+ * width: 100,
112
+ * height: 100,
113
+ * mimeType: "image/png",
114
+ * alt: "An example",
115
+ * hotspot: { x: 0.5, y: 0.5 },
116
+ * });
117
+ *
118
+ * @example
119
+ * // Backed by a gallery: width, height and mimeType live there.
120
+ * const schema = s.image(galleryVal);
121
+ * export default c.define("/example.val.ts", schema, {
122
+ * path: "/public/val/example.png",
123
+ * });
115
124
  *
116
125
  */
117
126
  readonly image: typeof image;
@@ -146,11 +155,15 @@ export type InitSchema = {
146
155
  /**
147
156
  * Define a file.
148
157
  *
149
- * Use `c.file` to create a file source.
158
+ * A file is an object with a `path`. `mimeType` comes from the extension —
159
+ * `npx val validate --fix` adds it.
150
160
  *
151
161
  * @example
152
162
  * const schema = s.file();
153
- * export default c.define("/example.val.ts", schema, c.file("/public/val/example.png"));
163
+ * export default c.define("/example.val.ts", schema, {
164
+ * path: "/public/val/example.pdf",
165
+ * mimeType: "application/pdf",
166
+ * });
154
167
  *
155
168
  */
156
169
  readonly file: typeof file;
@@ -291,11 +304,11 @@ export declare function initSchema(): {
291
304
  }) => import("./schema/number.js").NumberSchema<number>;
292
305
  union: <Key extends string | import("./schema/index.js").Schema<string>, T extends import("./schema/index.js").Schema<Key extends string ? import("./source/index.js").SourceObject & { [k in Key]: string; } : Key extends import("./schema/index.js").Schema<string> ? string : unknown>[]>(key: Key, ...objects: T) => import("./schema/union.js").UnionSchema<Key, T, T extends import("./schema/index.js").Schema<infer S extends import("./selector/index.js").SelectorSource>[] ? S extends import("./selector/index.js").SelectorSource ? S | (Key extends import("./schema/index.js").Schema<infer K extends import("./selector/index.js").SelectorSource> ? K : never) : never : never>;
293
306
  richtext: <O extends import("./index.js").RichTextOptions>(options?: O) => import("./schema/richtext.js").RichTextSchema<O, import("./index.js").RichTextSource<O>>;
294
- image: (options?: import("./schema/image.js").ImageOptions | import("./module.js").ValModule<Record<string, import("./schema/images.js").ImagesEntryMetadata>>) => import("./schema/image.js").ImageSchema<import("./index.js").ImageSource | import("./index.js").RemoteSource<import("./schema/image.js").ImageMetadata | undefined>>;
307
+ image: typeof image;
295
308
  literal: <T extends string>(value: T) => import("./schema/literal.js").LiteralSchema<T>;
296
309
  keyOf: <Src extends import("./selector/index.js").GenericSelector<import("./source/index.js").SourceObject> & import("./module.js").ValModuleBrand>(valModule: Src) => import("./schema/keyOf.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("./schema/string.js").RawString : keyof S : S extends Record<string, import("./source/index.js").Source> ? import("./schema/string.js").RawString : never : never>;
297
310
  record: typeof record;
298
- file: (options?: import("./schema/file.js").FileOptions | import("./module.js").ValModule<Record<string, import("./schema/files.js").FilesEntryMetadata>>) => import("./schema/file.js").FileSchema<import("./index.js").FileSource | import("./index.js").RemoteSource<import("./schema/file.js").FileMetadata | undefined>>;
311
+ file: typeof file;
299
312
  files: (options: import("./schema/files.js").FilesOptions) => import("./schema/record.js").RecordSchema<import("./schema/object.js").ObjectSchema<{
300
313
  mimeType: import("./schema/string.js").StringSchema<string>;
301
314
  }, {
@@ -306,7 +319,7 @@ export declare function initSchema(): {
306
319
  color: (options?: import("./schema/color.js").ColorOptions) => import("./schema/color.js").ColorSchema<import("./schema/string.js").RawString>;
307
320
  route: <T extends string>(options?: Record<string, never>) => import("./schema/route.js").RouteSchema<T>;
308
321
  router: typeof router;
309
- images: <Accept extends `image/${string}`>(options?: import("./schema/images.js").ImagesOptions<Accept>) => import("./schema/record.js").RecordSchema<import("./schema/object.js").ObjectSchema<{
322
+ images: <Accept extends `image/${string}`>(options: import("./schema/images.js").ImagesOptions<Accept>) => import("./schema/record.js").RecordSchema<import("./schema/object.js").ObjectSchema<{
310
323
  width: import("./schema/number.js").NumberSchema<number>;
311
324
  height: import("./schema/number.js").NumberSchema<number>;
312
325
  mimeType: import("./schema/string.js").StringSchema<string>;
@@ -1,15 +1,9 @@
1
1
  import { define } from "./module.js";
2
2
  import { InitSchema } from "./initSchema.js";
3
3
  import { getValPath as getPath } from "./val/index.js";
4
- import { initFile } from "./source/file.js";
5
- import { initImage } from "./source/image.js";
6
4
  import { json } from "./source/json.js";
7
- import { initRemote } from "./source/remote.js";
8
5
  export type ContentConstructor = {
9
6
  define: typeof define;
10
- file: ReturnType<typeof initFile>;
11
- image: ReturnType<typeof initImage>;
12
- remote: ReturnType<typeof initRemote>;
13
7
  json: typeof json;
14
8
  };
15
9
  export type ValConstructor = {
@@ -4,22 +4,19 @@ import { Source } from "./source/index.js";
4
4
  import { ModuleFilePath, ModulePath, SourcePath } from "./val/index.js";
5
5
  import { Json } from "./Json.js";
6
6
  import { RawString } from "./schema/string.js";
7
- import { ImageSelector } from "./selector/image.js";
8
- import { ImageSource } from "./source/image.js";
9
7
  declare const brand: unique symbol;
10
8
  export type ValModule<T extends SelectorSource> = SelectorOf<T> & ValModuleBrand;
11
9
  export type ValModuleBrand = {
12
10
  [brand]: "ValModule";
13
11
  };
14
12
  export type InferValModuleType<T extends ValModule<SelectorSource>> = T extends GenericSelector<infer S> ? S : never;
15
- export type ReplaceRawStringWithString<T extends SelectorSource> = SelectorSource extends T ? T : T extends RawString ? string : T extends ImageSelector ? ImageSource : T extends {
13
+ export type ReplaceRawStringWithString<T extends SelectorSource> = SelectorSource extends T ? T : T extends RawString ? string : T extends {
16
14
  [key in string]: SelectorSource;
17
15
  } ? {
18
16
  [key in keyof T]: ReplaceRawStringWithString<T[key]>;
19
17
  } : T extends SelectorSource[] ? ReplaceRawStringWithString<T[number]>[] : T;
20
18
  export declare function define<T extends Schema<SelectorSource>>(id: string, // TODO: `/${string}`
21
19
  schema: T, source: ReplaceRawStringWithString<SelectorOfSchema<T>>): ValModule<SelectorOfSchema<T>>;
22
- export declare function enrichFileImageRemoteSourceWithMetadata<T extends SelectorSource>(source: T, schema: Schema<SelectorSource>): T;
23
20
  export declare function getSource<T extends Source>(valModule: ValModule<T>): T;
24
21
  export declare function splitModuleFilePathAndModulePath(path: SourcePath | ModuleFilePath): [moduleId: ModuleFilePath, path: ModulePath];
25
22
  export declare function joinModuleFilePathAndModulePath(moduleFilePath: ModuleFilePath, modulePath: ModulePath): SourcePath;
@@ -1,8 +1,6 @@
1
1
  import { Schema } from "./schema/index.js";
2
- import { ImageMetadata } from "./schema/image.js";
3
2
  import { SelectorSource } from "./selector/index.js";
4
- import { ImageSource } from "./source/image.js";
5
- import { RemoteSource } from "./source/remote.js";
3
+ import { ImageSource } from "./source/media.js";
6
4
  import { ModuleFilePath, SourcePath } from "./val/index.js";
7
5
  export type ListRecordRender = {
8
6
  layout: "list";
@@ -12,18 +10,32 @@ export type ListRecordRender = {
12
10
  value: {
13
11
  title: string;
14
12
  subtitle?: string | null;
15
- image?: ImageSource | RemoteSource<ImageMetadata> | null;
13
+ image?: ImageSource | null;
16
14
  }
17
15
  ][];
18
16
  };
19
17
  export type ListArrayRender = {
20
18
  layout: "list";
21
19
  parent: "array";
22
- items: {
23
- title: string;
24
- subtitle?: string | null;
25
- image?: ImageSource | RemoteSource<ImageMetadata> | null;
26
- }[];
20
+ /**
21
+ * The rendered items, each PAIRED WITH ITS INDEX — the same
22
+ * `[key, value][]` shape {@link ListRecordRender} uses.
23
+ *
24
+ * Not a positionally-indexed array, and the reason is
25
+ * {@link RenderScope}: a render computed for a subset of paths carries only
26
+ * those items, so `items[i]` would silently read the wrong row. Carrying the
27
+ * index makes a windowed render impossible to misread — a consumer looks its
28
+ * index up rather than trusting a position — and makes the array and record
29
+ * shapes symmetric.
30
+ */
31
+ items: [
32
+ index: number,
33
+ value: {
34
+ title: string;
35
+ subtitle?: string | null;
36
+ image?: ImageSource | null;
37
+ }
38
+ ][];
27
39
  };
28
40
  export type TextareaRender = {
29
41
  layout: "textarea";
@@ -45,5 +57,42 @@ type WithStatus<T> = {
45
57
  data: T;
46
58
  };
47
59
  export type ReifiedRender = Record<SourcePath | ModuleFilePath, WithStatus<RenderTypes>>;
60
+ /**
61
+ * Which paths a render is being computed FOR.
62
+ *
63
+ * `executeRender` takes a whole module, so one request has always walked every
64
+ * node in it and run every `select` closure — for `handboka`, with `select` at
65
+ * two nested array levels, that is every chapter and every section to serve one
66
+ * visible row. A scope is what makes the walk proportional to what is being
67
+ * looked at instead of to the project.
68
+ *
69
+ * Two questions, because a container and its items need different answers:
70
+ *
71
+ * - {@link RenderScope.wants} — is a render AT this exact path wanted? A
72
+ * container answers `true` here when the whole of it is being shown, and its
73
+ * list render is then computed in full.
74
+ * - {@link RenderScope.wantsUnder} — could anything at or below this path be
75
+ * wanted? Recursion is pruned where this is `false`, and a container whose own
76
+ * path is not wanted but which has wanted descendants renders a WINDOW: only
77
+ * the items that were asked for. That is the case a single visible row is, and
78
+ * it is why {@link ListArrayRender} carries indices.
79
+ *
80
+ * Absent scope means the whole module, which is what every existing caller
81
+ * passes and what every existing caller got.
82
+ */
83
+ export type RenderScope = {
84
+ wants(path: SourcePath | ModuleFilePath): boolean;
85
+ wantsUnder(path: SourcePath | ModuleFilePath): boolean;
86
+ };
87
+ /**
88
+ * A scope covering exactly `paths` and their subtrees.
89
+ *
90
+ * Compares SEGMENTS rather than string prefixes. A source path's module path is
91
+ * a quoted, dot-joined encoding (`?p="a".1."b"`), so `startsWith` gets the
92
+ * ancestor test wrong wherever a key is a prefix of a sibling key or contains a
93
+ * dot or a quote — `"title"` against `"titles"` being the cheapest example.
94
+ * Segments are parsed once here, not per comparison.
95
+ */
96
+ export declare function renderScope(paths: readonly SourcePath[]): RenderScope;
48
97
  export type RenderSelector<T extends Schema<SelectorSource>> = T extends Schema<infer S> ? S : never;
49
98
  export {};
@@ -1,13 +1,23 @@
1
1
  import { Schema, SchemaAssertResult, SelectorOfSchema, SerializedSchema } from "./index.js";
2
- import { RenderSelector, ReifiedRender } from "../render.js";
2
+ import { RenderSelector, ReifiedRender, RenderScope } from "../render.js";
3
3
  import { SelectorSource } from "../selector/index.js";
4
- import { ImageSource } from "../source/image.js";
4
+ import { ImageSource } from "../source/media.js";
5
5
  import { ModuleFilePath, SourcePath } from "../val/index.js";
6
6
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
7
7
  export type SerializedArraySchema = {
8
8
  type: "array";
9
9
  item: SerializedSchema;
10
10
  opt: boolean;
11
+ /**
12
+ * Set when this schema declares a `render`.
13
+ *
14
+ * The render itself cannot be serialized — `select` is a user closure — but
15
+ * whether one EXISTS can be, and that is worth carrying: it lets the non-host
16
+ * side skip asking the host to render a module that cannot produce one.
17
+ * Measured in a browser: mounting 260 fields across 141 modules spent ~2.3ms
18
+ * of 3.1ms calling `executeRender` on modules that returned nothing.
19
+ */
20
+ render?: true;
11
21
  customValidate?: boolean;
12
22
  readonly?: boolean;
13
23
  hidden?: boolean;
@@ -41,7 +51,7 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
41
51
  hidden(): ArraySchema<T, Src>;
42
52
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
43
53
  protected executeSerialize(): SerializedArraySchema;
44
- protected executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src): ReifiedRender;
54
+ protected executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: RenderScope): ReifiedRender;
45
55
  render(input: ArrayRenderInput<T>): ArraySchema<T, Src>;
46
56
  }
47
57
  export declare const array: <S extends Schema<SelectorSource>>(schema: S) => ArraySchema<S, SelectorOfSchema<S>[]>;
@@ -1,9 +1,9 @@
1
- import { Json } from "../Json.js";
2
- import { FileSource } from "../source/file.js";
1
+ import { FileSource, GalleryFileSource } from "../source/media.js";
2
+ export type { FileMetadata } from "../source/file.js";
3
3
  import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
4
4
  import { ModulePath, SourcePath } from "../val/index.js";
5
5
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
6
- import { RemoteSource, ValModule } from "../index.js";
6
+ import { ValModule } from "../index.js";
7
7
  import { ReifiedRender } from "../render.js";
8
8
  import { FilesEntryMetadata } from "./files.js";
9
9
  export type FileOptions = {
@@ -20,10 +20,7 @@ export type SerializedFileSchema = {
20
20
  hidden?: boolean;
21
21
  description?: string;
22
22
  };
23
- export type FileMetadata = {
24
- mimeType?: string;
25
- };
26
- export declare class FileSchema<Src extends FileSource<FileMetadata | undefined> | RemoteSource<FileMetadata | undefined> | null> extends Schema<Src> {
23
+ export declare class FileSchema<Src extends FileSource | null> extends Schema<Src> {
27
24
  private readonly options?;
28
25
  private readonly opt;
29
26
  protected readonly isRemote: boolean;
@@ -34,9 +31,14 @@ export declare class FileSchema<Src extends FileSource<FileMetadata | undefined>
34
31
  private readonly description?;
35
32
  constructor(options?: FileOptions | undefined, opt?: boolean, isRemote?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], moduleMetadata?: Record<ModulePath, Record<string, FilesEntryMetadata>>, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined);
36
33
  describe(description: string | null): FileSchema<Src>;
37
- remote(): FileSchema<Src | RemoteSource<FileMetadata | undefined>>;
34
+ remote(): FileSchema<Src>;
38
35
  validate(validationFunction: CustomValidateFunction<Src>): FileSchema<Src>;
39
36
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
37
+ /**
38
+ * The entries of the gallery this field points at, or null when it is a
39
+ * standalone field.
40
+ */
41
+ private galleryEntries;
40
42
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
41
43
  nullable(): FileSchema<Src | null>;
42
44
  readonly(): FileSchema<Src>;
@@ -45,12 +47,10 @@ export declare class FileSchema<Src extends FileSource<FileMetadata | undefined>
45
47
  protected executeSerialize(): SerializedSchema;
46
48
  protected executeRender(): ReifiedRender;
47
49
  }
48
- export declare const file: (options?: FileOptions | ValModule<Record<string, FilesEntryMetadata>>) => FileSchema<FileSource | RemoteSource<FileMetadata | undefined>>;
49
- export declare function convertFileSource<Metadata extends {
50
- readonly [key: string]: Json;
51
- } | undefined = {
52
- readonly [key: string]: Json;
53
- } | undefined>(src: FileSource<Metadata>): {
54
- url: string;
55
- metadata?: Metadata;
56
- };
50
+ /**
51
+ * A file picked from a gallery. Its mime type lives in the gallery, so the
52
+ * field carries only the path.
53
+ */
54
+ export declare function file(galleryModule: ValModule<Record<string, FilesEntryMetadata>>): FileSchema<GalleryFileSource>;
55
+ /** A file of its own, carrying its own mime type. */
56
+ export declare function file(options?: FileOptions): FileSchema<FileSource>;
@@ -14,9 +14,13 @@ export type FilesOptions = {
14
14
  /**
15
15
  * The directory where files should be stored.
16
16
  * Must start with "/public" (e.g., "/public/val/files")
17
- * @default "/public/val"
17
+ *
18
+ * Required, and deliberately so: it decides where every uploaded file in this
19
+ * collection lands, and it used to default to "/public/val" — which meant a
20
+ * collection that had simply not said where it wanted its files silently shared
21
+ * a directory with every other one.
18
22
  */
19
- directory?: "/public" | `/public/${string}`;
23
+ directory: "/public" | `/public/${string}`;
20
24
  /**
21
25
  * Whether remote files are allowed
22
26
  * @default false
@@ -0,0 +1,34 @@
1
+ import type { SerializedSchema } from "./index.js";
2
+ /**
3
+ * Does this schema store any file remotely?
4
+ *
5
+ * The one answer to that question. There used to be two — `hasRemoteFileSchema`
6
+ * in the server, gating whether `/save` demands remote credentials, and
7
+ * `findRequiredRemoteFiles` in the Studio, gating the `/remote/settings` fetch —
8
+ * and they disagreed about `s.images({ remote: true })`: the Studio's counted a
9
+ * remote media record, the server's did not, because a media collection
10
+ * serializes as a `record` of metadata rather than as an image schema.
11
+ *
12
+ * The server's answer was the wrong one, and silently so. With it false,
13
+ * `/save` runs `saveOrUploadFiles` in `skip-remote` mode, and that mode does not
14
+ * merely skip the upload — it drops every remote descriptor without an error. The
15
+ * commit then lands a remote ref with no bytes behind it: a broken image, in the
16
+ * repository, with nothing anywhere saying why.
17
+ *
18
+ * ## Why it stays a walk over the SERIALIZED schema
19
+ *
20
+ * Both callers have a `SerializedSchema` and not a `Schema`: the server reads
21
+ * them out of `getSchemas()`, and the Studio is handed them by `host.receive`.
22
+ * Asking the schema classes directly would mean a method on every one of them,
23
+ * and the two places that need the answer cannot call it.
24
+ *
25
+ * ## Throwing
26
+ *
27
+ * The `never` assignment at the end is the point of the default branch: adding a
28
+ * schema type without teaching this function about it must not compile. The throw
29
+ * is what happens if one arrives anyway — which for a caller reading schemas out
30
+ * of its own process should be impossible. A caller that cannot afford to throw
31
+ * (the Studio, whose whole UI is downstream of this) catches it; returning `false`
32
+ * instead would reintroduce exactly the silent skip described above.
33
+ */
34
+ export declare function hasRemoteFileSchema(schema: SerializedSchema): boolean;
@@ -1,10 +1,8 @@
1
1
  import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
2
- import { FileSource } from "../source/file.js";
3
- import { ImageSource } from "../source/image.js";
2
+ import { GalleryImageSource, ImageSource } from "../source/media.js";
4
3
  import { ModulePath, SourcePath } from "../val/index.js";
5
4
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
6
5
  import { ValModule } from "../index.js";
7
- import { RemoteSource } from "../source/remote.js";
8
6
  import { ReifiedRender } from "../render.js";
9
7
  import { ImagesEntryMetadata } from "./images.js";
10
8
  export type ImageOptions = {
@@ -34,7 +32,7 @@ export type ImageMetadata = {
34
32
  y: number;
35
33
  };
36
34
  };
37
- export declare class ImageSchema<Src extends FileSource<ImageMetadata | undefined> | RemoteSource<ImageMetadata | undefined> | null> extends Schema<Src> {
35
+ export declare class ImageSchema<Src extends ImageSource | null> extends Schema<Src> {
38
36
  private readonly options?;
39
37
  private readonly opt;
40
38
  protected readonly isRemote: boolean;
@@ -45,9 +43,14 @@ export declare class ImageSchema<Src extends FileSource<ImageMetadata | undefine
45
43
  private readonly description?;
46
44
  constructor(options?: ImageOptions | undefined, opt?: boolean, isRemote?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], moduleMetadata?: Record<ModulePath, Record<string, ImagesEntryMetadata>>, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined);
47
45
  describe(description: string | null): ImageSchema<Src>;
48
- remote(): ImageSchema<Src | RemoteSource<ImageMetadata | undefined>>;
46
+ remote(): ImageSchema<Src>;
49
47
  validate(validationFunction: CustomValidateFunction<Src>): ImageSchema<Src>;
50
48
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
49
+ /**
50
+ * The entries of the gallery this field points at, or null when it is a
51
+ * standalone field.
52
+ */
53
+ private galleryEntries;
51
54
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
52
55
  nullable(): ImageSchema<Src | null>;
53
56
  readonly(): ImageSchema<Src>;
@@ -56,4 +59,10 @@ export declare class ImageSchema<Src extends FileSource<ImageMetadata | undefine
56
59
  protected executeSerialize(): SerializedSchema;
57
60
  protected executeRender(): ReifiedRender;
58
61
  }
59
- export declare const image: (options?: ImageOptions | ValModule<Record<string, ImagesEntryMetadata>>) => ImageSchema<ImageSource | RemoteSource<ImageMetadata | undefined>>;
62
+ /**
63
+ * An image picked from a gallery. Its dimensions and mime type live in the
64
+ * gallery, so the field carries only what a person typed.
65
+ */
66
+ export declare function image(galleryModule: ValModule<Record<string, ImagesEntryMetadata>>): ImageSchema<GalleryImageSource>;
67
+ /** An image of its own, carrying its own dimensions and mime type. */
68
+ export declare function image(options?: ImageOptions): ImageSchema<ImageSource>;
@@ -20,9 +20,13 @@ export type ImagesOptions<Accept extends `image/${string}`> = {
20
20
  /**
21
21
  * The directory where images should be stored.
22
22
  * Must start with "/public" (e.g., "/public/val/images")
23
- * @default "/public/val"
23
+ *
24
+ * Required, and deliberately so: it decides where every uploaded file in this
25
+ * collection lands, and it used to default to "/public/val" — which meant a
26
+ * gallery that had simply not said where it wanted its files silently shared a
27
+ * directory with every other one.
24
28
  */
25
- directory?: "/public" | `/public/${string}`;
29
+ directory: "/public" | `/public/${string}`;
26
30
  /**
27
31
  * Alt text schema. Can be:
28
32
  * - s.string() for required alt text
@@ -65,8 +69,9 @@ type ImagesItemSrc = {
65
69
  /**
66
70
  * Define a collection of images.
67
71
  *
68
- * All options are optional: `s.images()` accepts any image type (`"image/*"`) in
69
- * the default `/public/val` directory, with nullable alt text and remote disabled.
72
+ * `directory` is required — it decides where uploads land, so it is not something
73
+ * to be inferred. The rest defaults: any image type (`"image/*"`), nullable alt
74
+ * text, remote disabled.
70
75
  *
71
76
  * @example
72
77
  * ```typescript
@@ -85,5 +90,5 @@ type ImagesItemSrc = {
85
90
  * });
86
91
  * ```
87
92
  */
88
- export declare const images: <Accept extends `image/${string}`>(options?: ImagesOptions<Accept>) => RecordSchema<ObjectSchema<ImagesItemProps, ImagesItemSrc>, Schema<string>, Record<string, ImagesEntryMetadata>>;
93
+ export declare const images: <Accept extends `image/${string}`>(options: ImagesOptions<Accept>) => RecordSchema<ObjectSchema<ImagesItemProps, ImagesItemSrc>, Schema<string>, Record<string, ImagesEntryMetadata>>;
89
94
  export {};
@@ -17,9 +17,9 @@ import { SerializedDateSchema } from "./date.js";
17
17
  import { SerializedDateTimeSchema } from "./datetime.js";
18
18
  import { SerializedRouteSchema } from "./route.js";
19
19
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
20
- import { FileSource } from "../source/file.js";
20
+ import { FileSource } from "../source/media.js";
21
21
  import { GenericRichTextSourceNode, RichTextSource } from "../source/richtext.js";
22
- import { ReifiedRender } from "../render.js";
22
+ import { ReifiedRender, RenderScope } from "../render.js";
23
23
  export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedUnionSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedRouteSchema | SerializedImageSchema;
24
24
  type Primitives = number | string | boolean | null | FileSource;
25
25
  export type AssertError = {
@@ -106,7 +106,14 @@ export declare abstract class Schema<Src extends SelectorSource> {
106
106
  */
107
107
  abstract hidden(): Schema<Src>;
108
108
  protected abstract executeSerialize(): SerializedSchema;
109
- protected abstract executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src): ReifiedRender;
109
+ /**
110
+ * @param scope Which paths the caller needs a render for. Absent means the
111
+ * whole module, which is what every caller passed before scoping existed.
112
+ * See {@link RenderScope}: a container prunes recursion where nothing is
113
+ * wanted, and renders a WINDOW when its own path is not wanted but some of its
114
+ * items are — which is the single-visible-row case.
115
+ */
116
+ protected abstract executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: RenderScope): ReifiedRender;
110
117
  /** MUTATES! since internal and perf sensitive */
111
118
  protected appendValidationError(current: ValidationErrors, path: SourcePath, message: string, value: unknown, schemaError?: boolean): ValidationErrors;
112
119
  /**
@@ -1,5 +1,5 @@
1
1
  import { CustomValidateFunction, Schema, SchemaAssertResult, SelectorOfSchema, SerializedSchema } from "./index.js";
2
- import { ReifiedRender } from "../render.js";
2
+ import { ReifiedRender, RenderScope } from "../render.js";
3
3
  import { SelectorSource } from "../selector/index.js";
4
4
  import { ModuleFilePath, SourcePath } from "../val/index.js";
5
5
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
@@ -52,7 +52,7 @@ export declare class ObjectSchema<Props extends ObjectSchemaProps, Src extends O
52
52
  hidden(): ObjectSchema<Props, Src>;
53
53
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
54
54
  protected executeSerialize(): SerializedSchema;
55
- protected executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src): ReifiedRender;
55
+ protected executeRender(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: RenderScope): ReifiedRender;
56
56
  }
57
57
  export declare const object: <Props extends ObjectSchemaProps>(schema: Props) => ObjectSchema<Props, ObjectSchemaSrcOf<Props>>;
58
58
  export {};