@valbuild/core 0.126.0 → 0.128.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/CHANGELOG.md +296 -0
  2. package/dist/declarations/src/index.d.ts +5 -1
  3. package/dist/declarations/src/initSchema.d.ts +31 -15
  4. package/dist/declarations/src/locale.d.ts +55 -0
  5. package/dist/declarations/src/schema/array.d.ts +4 -0
  6. package/dist/declarations/src/schema/declaredKeys.d.ts +37 -0
  7. package/dist/declarations/src/schema/discriminatedUnion.d.ts +4 -0
  8. package/dist/declarations/src/schema/discriminatedUnionBranch.d.ts +20 -0
  9. package/dist/declarations/src/schema/enum.d.ts +9 -0
  10. package/dist/declarations/src/schema/file.d.ts +3 -3
  11. package/dist/declarations/src/schema/{files.d.ts → fileset.d.ts} +10 -10
  12. package/dist/declarations/src/schema/hasRemoteFileSchema.d.ts +3 -3
  13. package/dist/declarations/src/schema/image.d.ts +9 -5
  14. package/dist/declarations/src/schema/{images.d.ts → imageset.d.ts} +54 -21
  15. package/dist/declarations/src/schema/index.d.ts +116 -3
  16. package/dist/declarations/src/schema/literal.d.ts +2 -0
  17. package/dist/declarations/src/schema/locale.d.ts +132 -0
  18. package/dist/declarations/src/schema/object.d.ts +7 -0
  19. package/dist/declarations/src/schema/record.d.ts +57 -7
  20. package/dist/declarations/src/schema/settings.d.ts +54 -1
  21. package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
  22. package/dist/declarations/src/source/settings.d.ts +51 -0
  23. package/dist/{index-6db78f3c.cjs.prod.js → index-053ccde2.cjs.prod.js} +1114 -91
  24. package/dist/{index-b41b638f.cjs.dev.js → index-c3ae637d.cjs.dev.js} +1114 -91
  25. package/dist/{index-cd25c2dc.esm.js → index-db41de3c.esm.js} +1109 -92
  26. package/dist/valbuild-core.cjs.dev.js +7 -1
  27. package/dist/valbuild-core.cjs.prod.js +7 -1
  28. package/dist/valbuild-core.esm.js +1 -1
  29. package/package.json +1 -1
  30. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  31. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  32. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -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;
@@ -152,6 +152,37 @@ RO extends boolean = false> extends Schema<Src> {
152
152
  */
153
153
  validate(validationFunction: (src: Src) => false | string): RecordSchema<T, K, Src>;
154
154
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
155
+ /**
156
+ * The check that a record with a DECLARED key set holds every one of them.
157
+ *
158
+ * `s.record(s.locale(), item)` has one entry per language and
159
+ * `s.record(s.union(s.literal("a"), s.literal("b")), item)` has both — the
160
+ * keys are part of the schema either way, so a missing one is a hole in the
161
+ * content rather than content nobody has written yet. TypeScript already
162
+ * demanded both keys of the second; this is the validator catching up, which
163
+ * is why the two now behave the same.
164
+ *
165
+ * Deferred to `resolveSchemaSourceFixes` even for the literal case, where the
166
+ * keys are known right here: a locale record's keys are in another file, and
167
+ * one code path for both is one message, one fix and one thing to be wrong.
168
+ */
169
+ private getDeclaredKeysValidation;
170
+ /**
171
+ * The key set this record's schema declares, computed once.
172
+ *
173
+ * `declaredKeySetOf` reads a SERIALIZED schema, and serializing the key
174
+ * schema walks it — twice per validate, and once per record instance, for an
175
+ * answer that is a property of the schema and cannot move between them. A
176
+ * schema is built once and validated against many times, so the walk happens
177
+ * on the first question and not on any after it.
178
+ */
179
+ private declaredKeys;
180
+ private declaredKeySet;
181
+ protected opensLocaleScope(): "field" | "key" | null;
182
+ protected localeScopeChildren(): {
183
+ key: string;
184
+ schema: Schema<SelectorSource>;
185
+ }[];
155
186
  private isRemoteUrl;
156
187
  /** Marks the validation errors reported at `path` as key errors (in place). */
157
188
  private markKeyErrorsAtPath;
@@ -198,7 +229,7 @@ RO extends boolean = false> extends Schema<Src> {
198
229
  * Allow the files in this gallery to be stored on Val's remote content host
199
230
  * instead of in your repository.
200
231
  *
201
- * 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
202
233
  * until this is called.
203
234
  *
204
235
  * Each entry is then keyed by its URL on the content host rather than by a
@@ -207,7 +238,7 @@ RO extends boolean = false> extends Schema<Src> {
207
238
  * upload it and rewrite the key.
208
239
  *
209
240
  * @example
210
- * const schema = s.images({ directory: "/public/val/images" }).remote();
241
+ * const schema = s.imageset({ dir: "/public/val/images" }).remote();
211
242
  * export default c.define("/content/images.val.ts", schema, {
212
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":
213
244
  * {
@@ -226,7 +257,7 @@ RO extends boolean = false> extends Schema<Src> {
226
257
  * which lets the runtime, the Studio and validation work one entry at a time
227
258
  * so a record/router can scale to many thousands of entries.
228
259
  *
229
- * Not supported on image/file galleries (`s.images()` / `s.files()`).
260
+ * Not supported on image/file galleries (`s.imageset()` / `s.fileset()`).
230
261
  *
231
262
  * Only supported on a module's ROOT record/router — a `.jsonValues()` record
232
263
  * nested inside an object/array/record is rejected at startup with a module
@@ -253,7 +284,7 @@ RO extends boolean = false> extends Schema<Src> {
253
284
  * bucket — instead of in the module.
254
285
  *
255
286
  * Available on every record-derived schema, which is `s.record()`,
256
- * `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
257
288
  * `RecordSchema` with a `ValRouter`, a gallery one with media options, so all
258
289
  * four get external storage from this one method.
259
290
  *
@@ -344,6 +375,25 @@ RO extends boolean = false> extends Schema<Src> {
344
375
  */
345
376
  render(input: FieldRender): RecordSchema<T, K, Src>;
346
377
  }
347
- export declare function record<K extends Schema<string>, S extends Schema<SelectorSource>>(key: K, schema: S): RecordSchema<S, K, Record<SelectorOfSchema<K>, SelectorOfSchema<S>>>;
378
+ /**
379
+ * The source of a record, given what its key schema says about its keys.
380
+ *
381
+ * A key schema that ENUMERATES its keys (a union of literals, or `s.locale()`)
382
+ * makes every one of them an entry of the record, and an entry nobody has
383
+ * written yet is `null` rather than absent — so the value type widens by
384
+ * `null`. That is the whole of option F at the type level: a half-translated
385
+ * record is a record with nulls in it, which is data you can count, filter and
386
+ * see in a diff, rather than keys that are simply not there.
387
+ *
388
+ * For a union of literals TypeScript already required every key, since
389
+ * `SelectorOfSchema<K>` is the literal union itself. `s.locale()` is a flavoured
390
+ * string, so it requires nothing here and the requirement is the validator's —
391
+ * deliberately: which languages exist is in the settings module, where the
392
+ * project can change it without a deploy.
393
+ */
394
+ export type RecordSrcOf<K extends Schema<string>, S extends Schema<SelectorSource>> = K extends {
395
+ __declaresRecordKeys: true;
396
+ } ? Record<SelectorOfSchema<K>, SelectorOfSchema<S> | null> : Record<SelectorOfSchema<K>, SelectorOfSchema<S>>;
397
+ export declare function record<K extends Schema<string>, S extends Schema<SelectorSource>>(key: K, schema: S): RecordSchema<S, K, RecordSrcOf<K, S>>;
348
398
  export declare function record<S extends Schema<SelectorSource>>(schema: S): RecordSchema<S, Schema<string>, Record<string, SelectorOfSchema<S>>>;
349
399
  export {};
@@ -38,6 +38,19 @@ export type SerializedSettingsSchema = {
38
38
  readonly?: boolean;
39
39
  hidden?: boolean;
40
40
  description?: string;
41
+ /**
42
+ * Which of Val's sections this is, where it is one.
43
+ *
44
+ * The name rather than the rule itself, because the rule is a closure and a
45
+ * closure does not survive JSON. Val owns both ends — every section's rules
46
+ * are in {@link sectionValidators} in this file — so the name is enough for
47
+ * {@link deserializeSchema} to put the same rules back, and a Studio that
48
+ * does not recognise the name simply runs none (see `sectionValidate`).
49
+ *
50
+ * Absent on the settings module itself, which is a bag of sections and has no
51
+ * rules of its own.
52
+ */
53
+ section?: string;
41
54
  };
42
55
  /**
43
56
  * An object whose keys are all optional: absent means unset.
@@ -58,7 +71,47 @@ export declare class SettingsSchema<Src extends {
58
71
  private readonly isReadonly;
59
72
  private readonly isHidden;
60
73
  private readonly description?;
61
- constructor(items: Record<string, Schema<SelectorSource>>, opt?: boolean, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined);
74
+ /**
75
+ * Which section this is, where Val has rules about it beyond its keys.
76
+ *
77
+ * Not the `validate` the class deliberately does not offer: that one is the
78
+ * schema author's, and `s.settings()` takes no arguments to declare it
79
+ * with. This names a rule Val states about a shape Val owns — no language
80
+ * declared twice in `locales.available` is true of every project, and
81
+ * there is nowhere else it can be said. A single value's rules stay on its
82
+ * own schema; this is for the ones that need to see more than one value,
83
+ * or to report on a row of a list.
84
+ *
85
+ * A NAME rather than the function, so it survives serialization: the schema
86
+ * reaches the Studio's validation worker as JSON, and a closure would be
87
+ * dropped on the way — which is how these rules once went missing there.
88
+ */
89
+ private readonly section?;
90
+ constructor(items: Record<string, Schema<SelectorSource>>, opt?: boolean, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined,
91
+ /**
92
+ * Which section this is, where Val has rules about it beyond its keys.
93
+ *
94
+ * Not the `validate` the class deliberately does not offer: that one is the
95
+ * schema author's, and `s.settings()` takes no arguments to declare it
96
+ * with. This names a rule Val states about a shape Val owns — no language
97
+ * declared twice in `locales.available` is true of every project, and
98
+ * there is nowhere else it can be said. A single value's rules stay on its
99
+ * own schema; this is for the ones that need to see more than one value,
100
+ * or to report on a row of a list.
101
+ *
102
+ * A NAME rather than the function, so it survives serialization: the schema
103
+ * reaches the Studio's validation worker as JSON, and a closure would be
104
+ * dropped on the way — which is how these rules once went missing there.
105
+ */
106
+ section?: string | undefined);
107
+ /**
108
+ * This section's rules, or `undefined` where there are none to run.
109
+ *
110
+ * A name this Val does not know means a schema written by a newer one. Its
111
+ * per-field validation still runs; only the section's own rule is missed,
112
+ * which is the mild half of the failure and better than refusing the schema.
113
+ */
114
+ private sectionValidate;
62
115
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
63
116
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
64
117
  nullable(): SettingsSchema<Src | 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", "keyof:check-keys", "router:check-route", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "jsonValues:extract-entry", "external:upload"];
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", "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", "jsonValues:extract-entry", "record:fill-keys", "external:upload"];
2
2
  export type ValidationFix = (typeof ValidationFix)[number];
@@ -22,6 +22,41 @@
22
22
  */
23
23
  export type SettingsSource = {
24
24
  assistant?: AssistantSettingsSource;
25
+ locales?: LocalesSettingsSource;
26
+ };
27
+ /**
28
+ * The languages this project publishes.
29
+ *
30
+ * Content rather than configuration, and deliberately: which languages a site
31
+ * has is a decision the people who write it make, and under a build-time
32
+ * constant it took a developer and a deploy. It is the same move `assistant`
33
+ * makes with `enabled`.
34
+ *
35
+ * Val ships no list of its own. A project with no `locales` section has not
36
+ * said it is translated, and nothing about locales appears anywhere — no picker
37
+ * in the Studio, no checks, nothing.
38
+ *
39
+ * **This list is a decision with a blast radius.** Every locale in content is
40
+ * checked against it, so removing one invalidates the content that uses it, and
41
+ * adding one leaves every locale-keyed record short of a language until it is
42
+ * filled in. That is the intended behaviour — a language that is declared and
43
+ * missing everywhere is worth being told about — but it is why the Studio warns
44
+ * before saving a removal rather than treating this as an ordinary field.
45
+ *
46
+ * There is no default language, deliberately. Every locale-specific field asks
47
+ * which language it is in, and a default is exactly the answer that lets that
48
+ * question go unanswered — content ends up filed under a language nobody chose,
49
+ * which is the state translation is meant to make visible.
50
+ */
51
+ export type LocalesSettingsSource = {
52
+ /**
53
+ * The languages, as canonical BCP 47 tags: `en-US`, `nb-NO`.
54
+ *
55
+ * Order is the project's own, and it is kept: it decides the order of the
56
+ * Studio's picker and of the rows in a locale-keyed record, so a project can
57
+ * put the language it works in first.
58
+ */
59
+ available?: string[] | null;
25
60
  };
26
61
  /**
27
62
  * The assistant: whether editors have one, and what it is told about the
@@ -55,6 +90,22 @@ export type AssistantSettingsSource = {
55
90
  * `glossary`) rather than a field that looks like it should absorb them.
56
91
  */
57
92
  tone?: string | null;
93
+ /**
94
+ * How to translate into each language, keyed by language.
95
+ *
96
+ * Per language rather than one field, because translation rules are per
97
+ * language: bokmål or nynorsk, `du` or `De`, which product names stay in
98
+ * English. Only the target language's note is sent, so a French rule does not
99
+ * ride along in a Norwegian request.
100
+ *
101
+ * Keyed by language, so it holds every one of them once it exists at all —
102
+ * `null` where a language needs no special instruction, which is most of them.
103
+ * That is the same rule every locale-keyed record follows (see
104
+ * `declaredKeys.ts`): a gap you can count and see in a diff, rather than a key
105
+ * that is simply not there. A project with no notes at all leaves the whole
106
+ * field unset and nothing is required.
107
+ */
108
+ translation?: Record<string, string | null> | null;
58
109
  };
59
110
  /**
60
111
  * The cap on each of the assistant's prose fields, in characters.