@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.
- package/CHANGELOG.md +296 -0
- package/dist/declarations/src/index.d.ts +5 -1
- package/dist/declarations/src/initSchema.d.ts +31 -15
- package/dist/declarations/src/locale.d.ts +55 -0
- package/dist/declarations/src/schema/array.d.ts +4 -0
- package/dist/declarations/src/schema/declaredKeys.d.ts +37 -0
- package/dist/declarations/src/schema/discriminatedUnion.d.ts +4 -0
- package/dist/declarations/src/schema/discriminatedUnionBranch.d.ts +20 -0
- package/dist/declarations/src/schema/enum.d.ts +9 -0
- package/dist/declarations/src/schema/file.d.ts +3 -3
- package/dist/declarations/src/schema/{files.d.ts → fileset.d.ts} +10 -10
- package/dist/declarations/src/schema/hasRemoteFileSchema.d.ts +3 -3
- package/dist/declarations/src/schema/image.d.ts +9 -5
- package/dist/declarations/src/schema/{images.d.ts → imageset.d.ts} +54 -21
- package/dist/declarations/src/schema/index.d.ts +116 -3
- package/dist/declarations/src/schema/literal.d.ts +2 -0
- package/dist/declarations/src/schema/locale.d.ts +132 -0
- package/dist/declarations/src/schema/object.d.ts +7 -0
- package/dist/declarations/src/schema/record.d.ts +57 -7
- package/dist/declarations/src/schema/settings.d.ts +54 -1
- package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
- package/dist/declarations/src/source/settings.d.ts +51 -0
- package/dist/{index-6db78f3c.cjs.prod.js → index-053ccde2.cjs.prod.js} +1114 -91
- package/dist/{index-b41b638f.cjs.dev.js → index-c3ae637d.cjs.dev.js} +1114 -91
- package/dist/{index-cd25c2dc.esm.js → index-db41de3c.esm.js} +1109 -92
- package/dist/valbuild-core.cjs.dev.js +7 -1
- package/dist/valbuild-core.cjs.prod.js +7 -1
- package/dist/valbuild-core.esm.js +1 -1
- package/package.json +1 -1
- package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
- package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
- package/patch/dist/valbuild-core-patch.esm.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,301 @@
|
|
|
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
|
+
|
|
115
|
+
## 0.127.0
|
|
116
|
+
|
|
117
|
+
### Minor Changes
|
|
118
|
+
|
|
119
|
+
- [#608](https://github.com/valbuild/val/pull/608) [`7fa8699`](https://github.com/valbuild/val/commit/7fa869974bc895af992a7d5c18b76253636d7d65) Thanks [@freekh](https://github.com/freekh)! - A record whose schema declares its keys now holds every one of them.
|
|
120
|
+
|
|
121
|
+
Two key schemas enumerate their keys: `s.locale()`, whose set is the project's
|
|
122
|
+
`locales.available`, and a union of literals. For those, the keys are part of the
|
|
123
|
+
schema, so a missing one is a hole in the content rather than content nobody has
|
|
124
|
+
written yet — and validation now says so, naming what is missing.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
s.record(s.locale(), s.object({ title: s.string() }));
|
|
128
|
+
// Missing key: 'nb-NO'. This record's keys are declared by its schema, so
|
|
129
|
+
// every one of them is an entry — an entry nobody has written yet is null,
|
|
130
|
+
// not absent.
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**An entry nobody has written yet is `null`.** Not an absent key: a null entry is
|
|
134
|
+
data you can count, filter and see in a diff, and it means half-translated
|
|
135
|
+
content stays _valid_ rather than blocking a publish. The value type of such a
|
|
136
|
+
record widens by `null` to match, so writing one in a `.val.ts` type-checks:
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
c.define(
|
|
140
|
+
"/content/jacket.val.ts",
|
|
141
|
+
s.record(s.locale(), s.object({ title: s.string() })),
|
|
142
|
+
{
|
|
143
|
+
"en-US": { title: "Winter jacket" },
|
|
144
|
+
"nb-NO": null, // nobody has translated this yet
|
|
145
|
+
},
|
|
146
|
+
);
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**This changes `s.record(s.union(...), item)`**, and closes a gap that was
|
|
150
|
+
already there: `s.record(s.union(s.literal("a"), s.literal("b")), item)` types as
|
|
151
|
+
`Record<"a" | "b", T>`, so TypeScript demanded both keys while the validator only
|
|
152
|
+
checked the ones present. It now checks them too, and — as above — accepts `null`
|
|
153
|
+
for an entry that has not been filled in. If you have such a record with keys
|
|
154
|
+
missing, validation will report them; adding the keys with `null` values is the
|
|
155
|
+
fix, and creating one from the Studio does it for you.
|
|
156
|
+
|
|
157
|
+
`emptyOf` creates these records with every key already in them rather than
|
|
158
|
+
empty, since an empty one is already missing keys. In the Studio use the
|
|
159
|
+
`useEmptyOf()` hook rather than importing `emptyOf` directly: a locale record's
|
|
160
|
+
keys are in the settings module, and the hook is what has read it.
|
|
161
|
+
|
|
162
|
+
- [#608](https://github.com/valbuild/val/pull/608) [`5b7fe05`](https://github.com/valbuild/val/commit/5b7fe05cec6365f9cd1de9ba31e65ed4a87edb63) Thanks [@freekh](https://github.com/freekh)! - `s.locale()`: one of the project's languages.
|
|
163
|
+
|
|
164
|
+
The languages themselves are declared in the settings module (`locales.available`);
|
|
165
|
+
this says that a value is one of them.
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// a field: everything in this entry is in this language
|
|
169
|
+
s.record(s.string(), s.object({ locale: s.locale(), title: s.string() }));
|
|
170
|
+
|
|
171
|
+
// a key: one entry per language
|
|
172
|
+
s.record(s.locale(), s.object({ title: s.string() }));
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Every locale in content is checked against the project's list, the way `keyOf`
|
|
176
|
+
and `route` are checked against what they point at. An undeclared language names
|
|
177
|
+
the ones the project has; a project that has declared none is told to declare
|
|
178
|
+
them rather than told the value is wrong.
|
|
179
|
+
|
|
180
|
+
A locale is stored as the tag itself — the value in content is `nb-NO`, and a
|
|
181
|
+
record keyed by `s.locale()` has `nb-NO` as its key. Spelling one differently
|
|
182
|
+
where it is stored (`/no/…` as a URL segment) is a real need and is deliberately
|
|
183
|
+
not in this release: it changes what is accepted as well as what is shown, so it
|
|
184
|
+
is being designed on its own rather than folded in here.
|
|
185
|
+
|
|
186
|
+
A locale is **never stega encoded**: it ends up in `<html lang>`, in `hreflang`
|
|
187
|
+
and in `Intl` constructors, none of which survive invisible characters.
|
|
188
|
+
|
|
189
|
+
`assistant.translation` joins the settings module alongside `context` and `tone`
|
|
190
|
+
— a note per language, keyed by language, so only the target language's rules are
|
|
191
|
+
sent when translating into it.
|
|
192
|
+
|
|
193
|
+
- [#608](https://github.com/valbuild/val/pull/608) [`88262ac`](https://github.com/valbuild/val/commit/88262ac8db068650a664981ef73556457d87741a) Thanks [@freekh](https://github.com/freekh)! - Locale scopes: a subtree in one language, and one function that answers which.
|
|
194
|
+
|
|
195
|
+
A **locale scope** is content governed by a single language. Two things open one
|
|
196
|
+
today (a third, locale segments in routes, follows):
|
|
197
|
+
|
|
198
|
+
```typescript
|
|
199
|
+
// a locale field: this object and everything below it is in one language
|
|
200
|
+
s.object({ locale: s.locale(), title: s.string(), body: s.richtext() });
|
|
201
|
+
|
|
202
|
+
// a locale-keyed record: each entry is in the language its key names
|
|
203
|
+
s.record(s.locale(), s.object({ title: s.string() }));
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
**A scope may not contain another scope**, and an object may have only one
|
|
207
|
+
locale field. Both are reported as schema errors, naming what to move:
|
|
208
|
+
|
|
209
|
+
```
|
|
210
|
+
An object can be in one language, so it can have one locale field.
|
|
211
|
+
Found 'locale', 'language'.
|
|
212
|
+
|
|
213
|
+
Everything here is already in one language, so 'byLanguage' cannot set
|
|
214
|
+
another. Move the locale-keyed record out of this object, or take the outer
|
|
215
|
+
one away.
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
A scope three levels deep is reported once, by the scope immediately enclosing
|
|
219
|
+
it, rather than by every ancestor.
|
|
220
|
+
|
|
221
|
+
The rule is **validated rather than typed**. Expressing "no scope below this
|
|
222
|
+
one" as a type constraint means threading it through every schema class's type
|
|
223
|
+
parameter, and the errors a recursive constraint like that produces name the
|
|
224
|
+
whole tree — an unrelated typo in a `.val.ts` would print pages.
|
|
225
|
+
|
|
226
|
+
`localeAt(path, snapshot)` (from `@valbuild/shared/internal`) answers which
|
|
227
|
+
language governs a path, and is the one implementation of that question, so the
|
|
228
|
+
Studio, the server and the validation worker cannot disagree. It returns the
|
|
229
|
+
tag, which is what `<html lang>`, `Intl` and `locales.available` all want.
|
|
230
|
+
|
|
231
|
+
It answers `null` where no scope governs the path, where the project has
|
|
232
|
+
declared no languages, and where a locale field holds something that is not one
|
|
233
|
+
of them — validation is already reporting the last, and guessing would put a
|
|
234
|
+
language in `<html lang>` that nobody chose.
|
|
235
|
+
|
|
236
|
+
- [#608](https://github.com/valbuild/val/pull/608) [`29811c3`](https://github.com/valbuild/val/commit/29811c3f8c7e001a950e6f4833af6888e6a4efea) Thanks [@freekh](https://github.com/freekh)! - Settings: declare the languages a project publishes.
|
|
237
|
+
|
|
238
|
+
A new `locales` section in the settings module says which languages a project
|
|
239
|
+
has:
|
|
240
|
+
|
|
241
|
+
```typescript
|
|
242
|
+
export default c.define("/settings.val.ts", s.settings(), {
|
|
243
|
+
locales: {
|
|
244
|
+
available: ["en-US", "fr-FR", "nb-NO"],
|
|
245
|
+
},
|
|
246
|
+
});
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The order is the project's own and is kept rather than sorted: it is the order
|
|
250
|
+
of the Studio's locale picker and of the rows in a locale-keyed record. There
|
|
251
|
+
is no default language — every locale-specific field asks which language it is
|
|
252
|
+
in, and a default is the answer that lets that question go unanswered.
|
|
253
|
+
|
|
254
|
+
Like every other settings section it is optional, so a project that is not
|
|
255
|
+
translated writes nothing and sees nothing: no locale controls appear anywhere
|
|
256
|
+
until `available` has something in it.
|
|
257
|
+
|
|
258
|
+
This is content rather than configuration, and deliberately: which languages a
|
|
259
|
+
site has is a decision the people who write it make, and under a build-time
|
|
260
|
+
constant it took a developer and a deploy. It is the same move `assistant`
|
|
261
|
+
already makes with `enabled`.
|
|
262
|
+
|
|
263
|
+
Tags are BCP 47 (`en-US`, `nb-NO`), checked through `Intl.getCanonicalLocales` —
|
|
264
|
+
the same implementation `<html lang>` and every `Intl` constructor use — and
|
|
265
|
+
they have to be in canonical form. `nb-no` parses, but nothing else in the stack
|
|
266
|
+
agrees it is the same string as `nb-NO`, and a locale is compared as a string
|
|
267
|
+
everywhere it is used. Validation names the spelling to use, and reports a
|
|
268
|
+
language declared twice on the repeat rather than on the list — that is the row
|
|
269
|
+
to delete, and a message on the list itself would not say which.
|
|
270
|
+
|
|
271
|
+
Edited under Settings → Locales in the Studio, which names each language in its
|
|
272
|
+
own language.
|
|
273
|
+
|
|
274
|
+
### Patch Changes
|
|
275
|
+
|
|
276
|
+
- [#665](https://github.com/valbuild/val/pull/665) [`7072e07`](https://github.com/valbuild/val/commit/7072e07623c953a09ac14388ae22dada0b431ce3) Thanks [@freekh](https://github.com/freekh)! - `.nullable()` no longer drops the field's `.validate(...)` functions.
|
|
277
|
+
|
|
278
|
+
`.nullable()` returns a copy of the schema, and most of the schema classes built
|
|
279
|
+
that copy with an empty list of custom validators — so a validator declared
|
|
280
|
+
before the `.nullable()` was silently thrown away:
|
|
281
|
+
|
|
282
|
+
```ts
|
|
283
|
+
s.number()
|
|
284
|
+
.validate((n) => (n > 100 ? "Too big" : false))
|
|
285
|
+
.nullable(); // the validator never ran
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
`array`, `object`, `discriminatedUnion`, `enum`, `number`, `boolean`, `literal`,
|
|
289
|
+
`keyOf`, `date`, `dateTime`, `code`, `color` and `richtext` were affected;
|
|
290
|
+
`string`, `record`, `route`, `file` and `image` already carried them over, which
|
|
291
|
+
is why the bug was easy to miss. Validators now survive `.nullable()` on every
|
|
292
|
+
schema, in either order.
|
|
293
|
+
|
|
294
|
+
A validator on a nullable schema is called with `null` when the value is unset,
|
|
295
|
+
rather than being skipped — the same thing the schemas that never dropped them
|
|
296
|
+
have always done. If yours was written before the `.nullable()`, its argument is
|
|
297
|
+
typed as non-null even though `null` can reach it, so guard for it.
|
|
298
|
+
|
|
3
299
|
## 0.126.0
|
|
4
300
|
|
|
5
301
|
### Minor Changes
|
|
@@ -54,8 +54,12 @@ export { type SerializedImageSchema, ImageSchema } from "./schema/image.js";
|
|
|
54
54
|
export { type SerializedFileSchema, FileSchema } from "./schema/file.js";
|
|
55
55
|
export { type SerializedDateSchema, DateSchema } from "./schema/date.js";
|
|
56
56
|
export { type SerializedSettingsSchema, SettingsSchema, } from "./schema/settings.js";
|
|
57
|
-
export { type SettingsSource, type AssistantSettingsSource, type AssistantAvailability, ASSISTANT_SETTINGS_MAX_LENGTH, assistantAvailability, } from "./source/settings.js";
|
|
57
|
+
export { type SettingsSource, type AssistantSettingsSource, type LocalesSettingsSource, type AssistantAvailability, ASSISTANT_SETTINGS_MAX_LENGTH, assistantAvailability, } from "./source/settings.js";
|
|
58
58
|
export { type ResolvedSettingsModule, SETTINGS_MODULE_CONVENTION, isRootModuleFilePath, resolveSettingsModule, } from "./settingsModule.js";
|
|
59
|
+
export { type Locale, localeTagError, localeOfValue } from "./locale.js";
|
|
60
|
+
export { type SerializedLocaleSchema, LocaleSchema } from "./schema/locale.js";
|
|
61
|
+
export { type DeclaredKeySet, declaredKeySetOf, missingDeclaredKeys, } from "./schema/declaredKeys.js";
|
|
62
|
+
export { discriminatedUnionBranchOf } from "./schema/discriminatedUnionBranch.js";
|
|
59
63
|
export { type SerializedColorSchema, type ColorOptions, ColorSchema, } from "./schema/color.js";
|
|
60
64
|
export { type SerializedCodeSchema, type CodeOptions, type CodeLanguage, CodeSchema, CODE_LANGUAGES, } from "./schema/code.js";
|
|
61
65
|
export { type ColorFormat, type ParsedColor, COLOR_FORMATS, DEFAULT_COLOR_FORMAT, } from "./schema/colorFormat.js";
|
|
@@ -12,14 +12,15 @@ 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 {
|
|
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";
|
|
19
19
|
import { color } from "./schema/color.js";
|
|
20
|
+
import { locale } from "./schema/locale.js";
|
|
20
21
|
import { route } from "./schema/route.js";
|
|
21
22
|
import { router } from "./schema/router.js";
|
|
22
|
-
import {
|
|
23
|
+
import { imageset } from "./schema/imageset.js";
|
|
23
24
|
import { settings } from "./schema/settings.js";
|
|
24
25
|
export type InitSchema = {
|
|
25
26
|
/**
|
|
@@ -147,7 +148,7 @@ export type InitSchema = {
|
|
|
147
148
|
*
|
|
148
149
|
* @example
|
|
149
150
|
* // Backed by a gallery: width, height and mimeType live there.
|
|
150
|
-
* import galleryVal from "./gallery.val"; // an s.
|
|
151
|
+
* import galleryVal from "./gallery.val"; // an s.imageset() module
|
|
151
152
|
* const schema = s.image(galleryVal);
|
|
152
153
|
* export default c.define("/example.val.ts", schema, {
|
|
153
154
|
* path: "/public/val/example.png",
|
|
@@ -266,6 +267,20 @@ export type InitSchema = {
|
|
|
266
267
|
* ```
|
|
267
268
|
*/
|
|
268
269
|
readonly route: typeof route;
|
|
270
|
+
/**
|
|
271
|
+
* Define one of the project's languages.
|
|
272
|
+
*
|
|
273
|
+
* The languages themselves are declared in the settings module, under
|
|
274
|
+
* `locales.available` — this says only that a value is one of them, which is
|
|
275
|
+
* checked against that list.
|
|
276
|
+
*
|
|
277
|
+
* @example // a field: everything under this object is in this language
|
|
278
|
+
* const schema = s.object({ locale: s.locale(), title: s.string() });
|
|
279
|
+
*
|
|
280
|
+
* @example // a key: one entry per language
|
|
281
|
+
* const schema = s.record(s.locale(), s.object({ title: s.string() }));
|
|
282
|
+
*/
|
|
283
|
+
readonly locale: typeof locale;
|
|
269
284
|
/**
|
|
270
285
|
* Create a page router.
|
|
271
286
|
* Each key is the path of the page.
|
|
@@ -296,9 +311,9 @@ export type InitSchema = {
|
|
|
296
311
|
*
|
|
297
312
|
* @example
|
|
298
313
|
* ```typescript
|
|
299
|
-
* const schema = s.
|
|
314
|
+
* const schema = s.imageset({
|
|
300
315
|
* accept: "image/webp",
|
|
301
|
-
*
|
|
316
|
+
* dir: "/public/val/images",
|
|
302
317
|
* alt: s.string().minLength(4),
|
|
303
318
|
* });
|
|
304
319
|
* export default c.define("/content/images.val.ts", schema, {
|
|
@@ -311,15 +326,15 @@ export type InitSchema = {
|
|
|
311
326
|
* });
|
|
312
327
|
* ```
|
|
313
328
|
*/
|
|
314
|
-
readonly
|
|
329
|
+
readonly imageset: typeof imageset;
|
|
315
330
|
/**
|
|
316
331
|
* Define a collection of files.
|
|
317
332
|
*
|
|
318
333
|
* @example
|
|
319
334
|
* ```typescript
|
|
320
|
-
* const schema = s.
|
|
335
|
+
* const schema = s.fileset({
|
|
321
336
|
* accept: "application/pdf",
|
|
322
|
-
*
|
|
337
|
+
* dir: "/public/val/documents",
|
|
323
338
|
* });
|
|
324
339
|
* export default c.define("/content/documents.val.ts", schema, {
|
|
325
340
|
* "/public/val/documents/report.pdf": {
|
|
@@ -328,7 +343,7 @@ export type InitSchema = {
|
|
|
328
343
|
* });
|
|
329
344
|
* ```
|
|
330
345
|
*/
|
|
331
|
-
readonly
|
|
346
|
+
readonly fileset: typeof fileset;
|
|
332
347
|
/**
|
|
333
348
|
* Define the project's settings.
|
|
334
349
|
*
|
|
@@ -384,27 +399,28 @@ export declare function initSchema(): {
|
|
|
384
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>;
|
|
385
400
|
record: typeof record;
|
|
386
401
|
file: typeof file;
|
|
387
|
-
|
|
402
|
+
fileset: (options: import("./schema/fileset.js").FilesetOptions) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
|
|
388
403
|
mimeType: import("./index.js").StringSchema<string>;
|
|
389
404
|
}, {
|
|
390
405
|
mimeType: string;
|
|
391
|
-
}>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/
|
|
406
|
+
}>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/fileset.js").FilesetEntryMetadata>>;
|
|
392
407
|
date: (options?: Record<string, never>) => import("./index.js").DateSchema<import("./index.js").RawString>;
|
|
393
408
|
datetime: (options?: Record<string, never>) => import("./index.js").DateTimeSchema<import("./index.js").RawString>;
|
|
394
409
|
color: (options?: import("./index.js").ColorOptions) => import("./index.js").ColorSchema<import("./index.js").RawString>;
|
|
395
410
|
code: (options?: import("./index.js").CodeOptions) => import("./index.js").CodeSchema<import("./index.js").RawString>;
|
|
411
|
+
locale: () => import("./index.js").LocaleSchema<string>;
|
|
396
412
|
route: <T extends string>(options?: Record<string, never>) => import("./index.js").RouteSchema<T>;
|
|
397
413
|
router: typeof router;
|
|
398
|
-
|
|
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<{
|
|
399
415
|
width: import("./index.js").NumberSchema<number>;
|
|
400
416
|
height: import("./index.js").NumberSchema<number>;
|
|
401
417
|
mimeType: import("./index.js").StringSchema<string>;
|
|
402
|
-
alt:
|
|
418
|
+
alt: Alt;
|
|
403
419
|
}, {
|
|
404
420
|
width: number;
|
|
405
421
|
height: number;
|
|
406
422
|
mimeType: string;
|
|
407
|
-
alt:
|
|
408
|
-
}>, import("./schema/index.js").Schema<string>, Record<string, import("./schema/
|
|
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>>>>;
|
|
409
425
|
settings: typeof settings;
|
|
410
426
|
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Language tags, and what makes one wrong.
|
|
3
|
+
*
|
|
4
|
+
* Val does not ship a list of locales. Which languages a project publishes is a
|
|
5
|
+
* property of that project, so it is declared in the settings module — see
|
|
6
|
+
* `LocalesSettingsSource` — and everything downstream reads it from there.
|
|
7
|
+
*
|
|
8
|
+
* What Val does own is the SPELLING. A tag is checked against BCP 47
|
|
9
|
+
* (RFC 5646) through `Intl.getCanonicalLocales`, which is the same
|
|
10
|
+
* implementation the browser, `<html lang>` and every `Intl` constructor use.
|
|
11
|
+
* Delegating rather than writing a matcher is the point: a tag this accepts is
|
|
12
|
+
* a tag those accept.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* What is wrong with `tag` as a language tag, or `false` if nothing is.
|
|
16
|
+
*
|
|
17
|
+
* Canonical form, not merely parseable, and the difference is what makes this
|
|
18
|
+
* worth checking at all. `nb-no` parses; `Intl.DisplayNames` and a
|
|
19
|
+
* case-sensitive comparison against a stored key do not agree about it. Since a
|
|
20
|
+
* locale is compared as a string everywhere — a record key, a URL segment, a
|
|
21
|
+
* value in content — one spelling has to win, and the canonical one is the
|
|
22
|
+
* spelling every other tool already produces.
|
|
23
|
+
*
|
|
24
|
+
* The message names the canonical form where there is one, because "not
|
|
25
|
+
* canonical" is not an instruction and `nb-NO` is.
|
|
26
|
+
*/
|
|
27
|
+
export declare function localeTagError(tag: string): string | false;
|
|
28
|
+
/**
|
|
29
|
+
* A language tag, as content holds it.
|
|
30
|
+
*
|
|
31
|
+
* A *flavoured* string rather than a union or a strict brand. Which languages a
|
|
32
|
+
* project has is declared in its settings module, where the project can change
|
|
33
|
+
* it, so there is no set for a type to enumerate — and a strict brand would make
|
|
34
|
+
* `"nb-NO"` unassignable, which would mean writing every locale in a `.val.ts`
|
|
35
|
+
* through a constructor. Content stays plain data.
|
|
36
|
+
*
|
|
37
|
+
* So this carries no compile-time guarantee, and is not meant to. It names the
|
|
38
|
+
* concept where a bare `string` would say nothing, and the guarantee is the
|
|
39
|
+
* `locale:check-locale` validation against `locales.available`.
|
|
40
|
+
*/
|
|
41
|
+
declare const localeBrand: unique symbol;
|
|
42
|
+
export type Locale = string & {
|
|
43
|
+
readonly [localeBrand]?: "locale";
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* The locale a stored value means, or `null` if it means none of them.
|
|
47
|
+
*
|
|
48
|
+
* A lookup against the project's languages, and today that is all it is. It
|
|
49
|
+
* stays a named function because it is the one place the question is asked —
|
|
50
|
+
* the Studio, the validation worker and the server all come here rather than
|
|
51
|
+
* comparing strings themselves, so they cannot disagree about what a stored
|
|
52
|
+
* value is.
|
|
53
|
+
*/
|
|
54
|
+
export declare function localeOfValue(value: string, available: readonly string[]): string | null;
|
|
55
|
+
export {};
|
|
@@ -85,6 +85,10 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
|
|
|
85
85
|
nullable(): ArraySchema<T, Src | null>;
|
|
86
86
|
readonly(isReadonly?: boolean): ArraySchema<T, Src>;
|
|
87
87
|
hidden(isHidden?: boolean): ArraySchema<T, Src>;
|
|
88
|
+
protected localeScopeChildren(): {
|
|
89
|
+
key: string;
|
|
90
|
+
schema: Schema<SelectorSource>;
|
|
91
|
+
}[];
|
|
88
92
|
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
89
93
|
protected executeSerialize(): SerializedArraySchema;
|
|
90
94
|
protected executePreview(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: PreviewScope): ReifiedPreview;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { SerializedSchema } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The keys a record is REQUIRED to hold, where its key schema says what they are.
|
|
4
|
+
*
|
|
5
|
+
* Most records have an open key set — `s.record(s.string(), item)` holds
|
|
6
|
+
* whatever anyone adds — and this returns `null` for those. A record keyed by
|
|
7
|
+
* something that enumerates its values is different in kind: the keys are part
|
|
8
|
+
* of the schema, so a missing one is a hole in the content rather than content
|
|
9
|
+
* nobody has written yet.
|
|
10
|
+
*
|
|
11
|
+
* Two key schemas enumerate. A union of literals does it locally, so its keys
|
|
12
|
+
* are here. `s.locale()` does it in the settings module, which is another file
|
|
13
|
+
* and so cannot be read from a serialized schema — that case says only which
|
|
14
|
+
* kind it is, and is resolved against `locales.available` by
|
|
15
|
+
* `resolveSchemaSourceFixes`, the same crossing `keyOf` and `route` make.
|
|
16
|
+
*/
|
|
17
|
+
export type DeclaredKeySet = {
|
|
18
|
+
kind: "literals";
|
|
19
|
+
keys: string[];
|
|
20
|
+
} | {
|
|
21
|
+
kind: "locale";
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* What `key` requires a record to hold, or `null` where it requires nothing.
|
|
25
|
+
*
|
|
26
|
+
* Works from the SERIALIZED schema so that the Studio, the validation worker
|
|
27
|
+
* and the server all answer this the same way — none of them has the schema
|
|
28
|
+
* instance, and `SchemaStore` deliberately holds serialized schemas only.
|
|
29
|
+
*/
|
|
30
|
+
export declare function declaredKeySetOf(key: SerializedSchema | undefined): DeclaredKeySet | null;
|
|
31
|
+
/**
|
|
32
|
+
* The declared keys a record is missing, in the order the schema declares them.
|
|
33
|
+
*
|
|
34
|
+
* Declaration order rather than the source's: this is read as "what is left to
|
|
35
|
+
* write", and the answer should not reshuffle itself as entries are added.
|
|
36
|
+
*/
|
|
37
|
+
export declare function missingDeclaredKeys(declared: readonly string[], present: readonly string[]): string[];
|
|
@@ -114,6 +114,10 @@ export declare class DiscriminatedUnionSchema<Key extends string, T extends Disc
|
|
|
114
114
|
nullable(): DiscriminatedUnionSchema<Key, T, Src | null>;
|
|
115
115
|
readonly(isReadonly?: boolean): DiscriminatedUnionSchema<Key, T, Src>;
|
|
116
116
|
hidden(isHidden?: boolean): DiscriminatedUnionSchema<Key, T, Src>;
|
|
117
|
+
protected localeScopeChildren(): {
|
|
118
|
+
key: string;
|
|
119
|
+
schema: Schema<SelectorSource>;
|
|
120
|
+
}[];
|
|
117
121
|
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
118
122
|
/**
|
|
119
123
|
* The variant schema a value takes. `null` when the value matches none of
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { SerializedSchema } from "./index.js";
|
|
2
|
+
/**
|
|
3
|
+
* The variant of a discriminated union that a value takes, by its tag.
|
|
4
|
+
*
|
|
5
|
+
* A discriminated union is a fork rather than a level: the variant IS the node,
|
|
6
|
+
* and everything a caller wants to know about that node — its fields, its
|
|
7
|
+
* `locale` field, the schema to draw it with — is on the variant and not on the
|
|
8
|
+
* union. Resolving it takes the source, so it cannot be done from the schema
|
|
9
|
+
* alone, which is why every caller that has only the union ends up needing this.
|
|
10
|
+
*
|
|
11
|
+
* `undefined` where the tag matches nothing, rather than a fallback to the first
|
|
12
|
+
* variant: a value whose tag names no variant is not a member of the union,
|
|
13
|
+
* validation is already saying so, and reading fields out of the wrong shape
|
|
14
|
+
* would answer confidently and wrongly.
|
|
15
|
+
*
|
|
16
|
+
* `s.enum()` never reaches here — it is a leaf whose values are strings, with no
|
|
17
|
+
* variants to resolve — so anything that is not a discriminated union answers
|
|
18
|
+
* `undefined` too.
|
|
19
|
+
*/
|
|
20
|
+
export declare function discriminatedUnionBranchOf(schema: SerializedSchema | undefined, tag: unknown): SerializedSchema | undefined;
|
|
@@ -38,6 +38,15 @@ export declare class EnumSchema<Src extends string | null> extends Schema<Src> {
|
|
|
38
38
|
private readonly description?;
|
|
39
39
|
private readonly renderInput;
|
|
40
40
|
private readonly previewInput;
|
|
41
|
+
/**
|
|
42
|
+
* Type-only marker: as a record key, this declares the key set.
|
|
43
|
+
*
|
|
44
|
+
* An enum names every value it may take, so a record keyed by one holds
|
|
45
|
+
* exactly those keys — the same claim `s.literal()` and `s.locale()` make.
|
|
46
|
+
* `DiscriminatedUnionSchema` carries no marker: it discriminates objects and
|
|
47
|
+
* cannot be a record key at all. See `LocaleSchema`.
|
|
48
|
+
*/
|
|
49
|
+
readonly __declaresRecordKeys: true;
|
|
41
50
|
constructor(values: readonly string[], opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null, previewInput?: ItemPreviewInput<Src> | null);
|
|
42
51
|
/**
|
|
43
52
|
* Describe this field.
|
|
@@ -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 {
|
|
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,
|
|
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,
|
|
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>;
|