@valbuild/core 0.129.0 → 0.134.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 +188 -0
- package/dist/declarations/src/index.d.ts +11 -1
- package/dist/declarations/src/initSchema.d.ts +20 -0
- package/dist/declarations/src/module.d.ts +21 -4
- package/dist/declarations/src/preview.d.ts +65 -8
- package/dist/declarations/src/render.d.ts +26 -0
- package/dist/declarations/src/schema/array.d.ts +8 -5
- package/dist/declarations/src/schema/boolean.d.ts +8 -6
- package/dist/declarations/src/schema/code.d.ts +8 -6
- package/dist/declarations/src/schema/color.d.ts +8 -6
- package/dist/declarations/src/schema/date.d.ts +8 -6
- package/dist/declarations/src/schema/datetime.d.ts +8 -6
- package/dist/declarations/src/schema/discriminatedUnion.d.ts +8 -5
- package/dist/declarations/src/schema/enum.d.ts +8 -6
- package/dist/declarations/src/schema/file.d.ts +8 -6
- package/dist/declarations/src/schema/image.d.ts +8 -6
- package/dist/declarations/src/schema/index.d.ts +31 -2
- package/dist/declarations/src/schema/keyOf.d.ts +8 -6
- package/dist/declarations/src/schema/literal.d.ts +8 -6
- package/dist/declarations/src/schema/locale.d.ts +1 -2
- package/dist/declarations/src/schema/number.d.ts +8 -6
- package/dist/declarations/src/schema/object.d.ts +18 -5
- package/dist/declarations/src/schema/record.d.ts +8 -5
- package/dist/declarations/src/schema/richtext.d.ts +8 -6
- package/dist/declarations/src/schema/route.d.ts +8 -6
- package/dist/declarations/src/schema/settings.d.ts +1 -1
- package/dist/declarations/src/schema/string.d.ts +8 -11
- package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
- package/dist/declarations/src/schema/view.d.ts +197 -0
- package/dist/declarations/src/selector/index.d.ts +4 -2
- package/dist/declarations/src/selector/view.d.ts +22 -0
- package/dist/declarations/src/source/index.d.ts +2 -1
- package/dist/declarations/src/source/settings.d.ts +27 -0
- package/dist/declarations/src/source/view.d.ts +41 -0
- package/dist/declarations/src/source/viewHandle.d.ts +69 -0
- package/dist/{index-f5842720.cjs.prod.js → index-48a8f8a5.cjs.prod.js} +1281 -353
- package/dist/{index-21fd16b1.cjs.dev.js → index-85a2ffba.cjs.dev.js} +1281 -353
- package/dist/{index-06bb2832.esm.js → index-e3458565.esm.js} +1280 -354
- package/dist/valbuild-core.cjs.dev.js +3 -1
- package/dist/valbuild-core.cjs.prod.js +3 -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,193 @@
|
|
|
1
1
|
# @valbuild/core
|
|
2
2
|
|
|
3
|
+
## 0.134.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#690](https://github.com/valbuild/val/pull/690) [`16c49ea`](https://github.com/valbuild/val/commit/16c49ea6dd97c7a96bbfdf211af9bab5884579e2) Thanks [@freekh](https://github.com/freekh)! - Read a view with `useVal` / `fetchVal`.
|
|
8
|
+
|
|
9
|
+
A `s.view()` field reads as a pointer with no properties on it. It can now be handed to a reader, which resolves it to the module it names:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
const page = useVal(pageVal);
|
|
13
|
+
const header = useVal(page.header); // the header module's content, typed
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The point is single source of truth rather than a new capability: the page declares which module it shows, and a component follows that declaration instead of importing the target a second time. Change `s.view(headerVal)` to `s.view(navVal)` and the reader follows; an import would have kept reading the header.
|
|
17
|
+
|
|
18
|
+
The readers that take a MODULE rather than a value follow the same rule — `useValKey`, `useValRoute`, `useValRouteUrl` and the `fetchVal*` counterparts, in both the Next and TanStack packages:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const page = useVal(pageVal);
|
|
22
|
+
const note = useValRoute(page.notes, params); // the router module the view names
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Works in draft mode, including when the page itself has a pending edit. Two things to know:
|
|
26
|
+
|
|
27
|
+
- **Resolve a handle in the component that read the module containing it.** A handle carries the module it points at, and that cannot survive serialization — so one passed from a server component to a client component as a prop arrives empty. It throws with an explanation rather than handing back the pointer. This is why it throws rather than returning nothing: every one of the route and key readers already uses `null` / `undefined` to mean "no such entry", so a quiet answer would be indistinguishable from a 404.
|
|
28
|
+
- **`ValView<Source>` is now a member of `SelectorSource`**, since a reader accepts one.
|
|
29
|
+
|
|
30
|
+
- [#680](https://github.com/valbuild/val/pull/680) [`eaa265e`](https://github.com/valbuild/val/commit/eaa265e78d9f6d2a1b685c38901612b8a3704eed) Thanks [@freekh](https://github.com/freekh)! - Add `s.view()`: point at another module, so editors can reach it from the page it belongs to.
|
|
31
|
+
|
|
32
|
+
Content that has to live in its own module — a `keyOf` target, shared settings, a route-keyed record — is invisible from the page an editor thinks of it as part of. A view field puts a row on that page's screen that leads to it:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import employeesVal from "../data/employees.val";
|
|
36
|
+
|
|
37
|
+
const schema = s.object({
|
|
38
|
+
title: s.string(),
|
|
39
|
+
employees: s.view(employeesVal),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default c.define("/app/menneskene/page.val.ts", schema, {
|
|
43
|
+
title: "Våre folk",
|
|
44
|
+
employees: { view: "/data/employees.val.ts" },
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The source is a pointer and nothing else. The module it names keeps its own source, patches, validation and address, and an editor who follows the row lands on that module's own screen — so it is clear they are changing something other pages use too.
|
|
49
|
+
|
|
50
|
+
A few things worth knowing:
|
|
51
|
+
|
|
52
|
+
- **The path autocompletes, and a wrong one does not compile.** A module now carries its own id in its type, so the source type of the field above is the literal `{ view: "/data/employees.val.ts" }`.
|
|
53
|
+
- **It is not readable in code.** `useVal(pageVal).employees` is a `ValView<…>` — an opaque pointer with no fields on it. Read the module it names directly, as before.
|
|
54
|
+
- **`view` is now a reserved object key**, like `_type` and `patch_id`: `s.object({ view: ... })` no longer compiles. A single `view: string` key is what a view pointer looks like, and an ordinary object with that shape would be indistinguishable from one.
|
|
55
|
+
- **Views may not form a cycle.** `A → B → A`, and a module viewing itself, are reported as module errors by `val validate` and in the Studio.
|
|
56
|
+
- **A pointer that disagrees with its schema is repaired automatically.** It can only happen in hand-written JSON, and the schema is the authority, so `val validate --fix` and saving in the Studio both write the module the schema names.
|
|
57
|
+
- **A view's `hidden` and `readonly` are its own, never the module's it points at.** A view whose target is hidden is still shown, and still leads there.
|
|
58
|
+
|
|
59
|
+
That last one comes with a change to what `hidden()` means on a **module's own schema**, which is the other half of making a shared module usable:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// Not in the nav, and on exactly one page.
|
|
63
|
+
export default c.define("/data/employees.val.ts", s.record(...).hidden(), { ... });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
It now means the nav does not list the module — the Explorer for an ordinary module, Media for a gallery — and nothing more. Previously it also blanked the module's own page, so a module you had hidden was still in the nav and showed nothing when opened. A hidden module is now reached from an `s.view()` row, from search or from a validation error, and renders in full when you get there.
|
|
67
|
+
|
|
68
|
+
One gap worth naming rather than leaving to be discovered: a view pointing at a
|
|
69
|
+
module the project does not have is reported by the FIELD — the row says the
|
|
70
|
+
target is missing — but not by `val validate`. The schema check compares the
|
|
71
|
+
pointer against the schema, and the cycle check deliberately skips a target that
|
|
72
|
+
is not a module of the project, so neither catches it. A project-level check
|
|
73
|
+
belongs with them and is not here yet.
|
|
74
|
+
|
|
75
|
+
## 0.130.0
|
|
76
|
+
|
|
77
|
+
### Patch Changes
|
|
78
|
+
|
|
79
|
+
- [#663](https://github.com/valbuild/val/pull/663) [`8425378`](https://github.com/valbuild/val/commit/8425378c315ea46b5d822f1130b633e0449ff1b0) Thanks [@freekh](https://github.com/freekh)! - A value that has not been written yet offers to create it, instead of looking broken
|
|
80
|
+
|
|
81
|
+
Opening an array item or a record entry whose value is `null` rendered the item
|
|
82
|
+
schema's fields over nothing, so every one of them reported **Not Found** — a
|
|
83
|
+
column of broken fields where the truth is one fact about the value: nobody has
|
|
84
|
+
written it. It now shows one **Create** button.
|
|
85
|
+
|
|
86
|
+
A field inside an object always had this — the checkbox beside its label — but
|
|
87
|
+
a value opened on its own has no such wrapper, and that is exactly what
|
|
88
|
+
navigating to an entry does. Both places now agree.
|
|
89
|
+
|
|
90
|
+
This is most visible with a record whose keys are declared by its schema
|
|
91
|
+
(`s.record(s.locale(), …)` or `s.record(s.enum("a", "b"), …)`), where an entry
|
|
92
|
+
nobody has written is `null` rather than absent. There the wording follows: an
|
|
93
|
+
unwritten entry reads as **Not translated** in the list, and the button says
|
|
94
|
+
**Write this translation** rather than Create.
|
|
95
|
+
|
|
96
|
+
Also fixed: `Internal.resolvePath` reported a record entry that exists with a
|
|
97
|
+
falsy value — `null`, but equally `""`, `0` and `false`, in any record — as a
|
|
98
|
+
key the record does not have, so nothing could resolve a path to one.
|
|
99
|
+
|
|
100
|
+
- [#679](https://github.com/valbuild/val/pull/679) [`473a185`](https://github.com/valbuild/val/commit/473a185f70351b44388f3bc1852649e2c1dbe001) Thanks [@freekh](https://github.com/freekh)! - `.preview()` now names a value wherever the Studio shows it, including a module's own root
|
|
101
|
+
|
|
102
|
+
Wherever the Studio shows a piece of content to a human it has to answer three
|
|
103
|
+
questions: what is this called, what is it, what does it look like. Every
|
|
104
|
+
surface answered them itself, out of the path, and they disagreed — the same
|
|
105
|
+
record entry read `blog1` in the heading, "Blog 1" in the scope trail and
|
|
106
|
+
`/blogs/blog1` in the sitemap. They now give one answer, and it is the one you
|
|
107
|
+
wrote:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
s.record(
|
|
111
|
+
s.object({ title: s.string(), author: s.string(), cover: s.image() }),
|
|
112
|
+
).preview((blog) => ({
|
|
113
|
+
title: blog.title,
|
|
114
|
+
subtitle: blog.author,
|
|
115
|
+
image: blog.cover,
|
|
116
|
+
}));
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
That title, subtitle and image are what the heading, list rows, search hits,
|
|
120
|
+
cards and chosen references show. Nothing is required: a project with no
|
|
121
|
+
`.preview()` reads exactly as it did — the route, the key, `[#3](https://github.com/valbuild/val/issues/3)`, or the
|
|
122
|
+
prettified file name — so this is somewhere to improve from rather than
|
|
123
|
+
something to adopt.
|
|
124
|
+
|
|
125
|
+
**`.preview()` on a module's own schema now works.** It was accepted and never
|
|
126
|
+
run: a preview was only ever reified by a CONTAINER for its rows, and a module
|
|
127
|
+
root has no container. So `c.define("/content/authors.val.ts", s.record(…)
|
|
128
|
+
.preview(…), …)` can name the module itself — "Forfattere" rather than
|
|
129
|
+
`authors.val.ts` — and the same is true of a field of an object.
|
|
130
|
+
|
|
131
|
+
**A page's URL is carried separately from its title.** A route is not a worse
|
|
132
|
+
name for a page, it is the page's identity: two drafts both titled "Launch" are
|
|
133
|
+
told apart by `/blog/launch-2026` and by nothing else. Title a page with
|
|
134
|
+
`.preview()` and its route moves to the line under the heading rather than
|
|
135
|
+
disappearing.
|
|
136
|
+
|
|
137
|
+
Two things a preview is deliberately NOT used for, because a preview is a
|
|
138
|
+
closure over source and so changes as an editor types:
|
|
139
|
+
|
|
140
|
+
- The breadcrumb, the Explorer and the Pages tree stay path segments. A trail
|
|
141
|
+
of titles names three things and locates none of them. The one exception is a
|
|
142
|
+
page, whose trail is its ROUTE instead of the file it is stored in — nobody
|
|
143
|
+
reaches a page through the file.
|
|
144
|
+
- Help text. `.describe()` is input help and is shown where a field or a key is
|
|
145
|
+
being ENTERED; a record key's description no longer appears in the heading,
|
|
146
|
+
where the key cannot be edited, and appears in every form that asks for one.
|
|
147
|
+
|
|
148
|
+
Also fixed: a just-uploaded image stayed blank until save in list rows, headings
|
|
149
|
+
and reference dropdowns, which built the URL of the published file rather than
|
|
150
|
+
the pending patch's.
|
|
151
|
+
|
|
152
|
+
- [#677](https://github.com/valbuild/val/pull/677) [`64f0de3`](https://github.com/valbuild/val/commit/64f0de339b8621cb5a6c422dfe55cae5b2bbe2a0) Thanks [@freekh](https://github.com/freekh)! - A guided tour of the Studio, and a `studio.tour` setting to turn it off
|
|
153
|
+
|
|
154
|
+
Editors kept asking what Pages, Media and Data are for. The three words are
|
|
155
|
+
precise inside Val and vague everywhere else, and the Studio said each of them
|
|
156
|
+
in three places — the rail tooltip, the panel header, the empty state — without
|
|
157
|
+
ever defining any.
|
|
158
|
+
|
|
159
|
+
**A guided tour**, offered by a glowing "Take a tour" button on the empty editor
|
|
160
|
+
at `/val/~` and kept permanently in **Quick actions**: welcome, then Pages, Media
|
|
161
|
+
and Data where the project has them, then the assistant where there is one, then
|
|
162
|
+
Review, Preview and Publish. It never opens itself, and the glow stops for good
|
|
163
|
+
once somebody has been through it on that browser.
|
|
164
|
+
|
|
165
|
+
Turn it off for the whole project under **Settings → Studio** — a new
|
|
166
|
+
`studio.tour` field on `s.settings()`, unset meaning the tour is offered. A team
|
|
167
|
+
that finds it noisy switches it off once, for everyone, instead of each person
|
|
168
|
+
dismissing it on each machine; the tour stays in Quick actions for anyone who
|
|
169
|
+
wants it.
|
|
170
|
+
|
|
171
|
+
Also, for the same first-run problem:
|
|
172
|
+
|
|
173
|
+
- **Rail tooltips carry a definition** under the label: "The pages of your site,
|
|
174
|
+
by URL", "Shared images and files, uploaded once", "Content that is not tied
|
|
175
|
+
to one page".
|
|
176
|
+
- **The empty editor is a short glossary** of the destinations this project
|
|
177
|
+
actually has, rather than "No item selected".
|
|
178
|
+
- **Empty states explain instead of reporting.** "No pages yet" now says who
|
|
179
|
+
creates the routes pages go under; "No galleries yet" says what a gallery is
|
|
180
|
+
for.
|
|
181
|
+
- **A project with a single media gallery opens it**, so Media shows media
|
|
182
|
+
instead of one collapsed row named after a module file.
|
|
183
|
+
- **A site map of twenty pages or fewer arrives open.** With a home page at `/`
|
|
184
|
+
the whole site nests under one root row, so Pages used to show a single row
|
|
185
|
+
called Home. Larger sites keep the old behaviour.
|
|
186
|
+
- **Settings points at Account** for the per-person settings — the theme, and
|
|
187
|
+
how the Studio behaves on this machine.
|
|
188
|
+
- Page rows with children now carry `aria-expanded`, as the media panel's rows
|
|
189
|
+
always did.
|
|
190
|
+
|
|
3
191
|
## 0.129.0
|
|
4
192
|
|
|
5
193
|
### Minor Changes
|
|
@@ -54,7 +54,7 @@ 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 LocalesSettingsSource, type AssistantAvailability, type ThemeSettingsSource, type ThemeRadius, ASSISTANT_SETTINGS_MAX_LENGTH, THEME_LOGO_DIRECTORY, THEME_RADIUS_STEPS, THEME_RADIUS_LENGTHS, assistantAvailability, } from "./source/settings.js";
|
|
57
|
+
export { type SettingsSource, type AssistantSettingsSource, type LocalesSettingsSource, type AssistantAvailability, type ThemeSettingsSource, type StudioSettingsSource, type ThemeRadius, ASSISTANT_SETTINGS_MAX_LENGTH, THEME_LOGO_DIRECTORY, THEME_RADIUS_STEPS, THEME_RADIUS_LENGTHS, assistantAvailability, } from "./source/settings.js";
|
|
58
58
|
export { type ResolvedSettingsModule, SETTINGS_MODULE_CONVENTION, isRootModuleFilePath, resolveSettingsModule, } from "./settingsModule.js";
|
|
59
59
|
export { type Locale, localeTagError, localeOfValue } from "./locale.js";
|
|
60
60
|
export { type SerializedLocaleSchema, LocaleSchema } from "./schema/locale.js";
|
|
@@ -65,6 +65,11 @@ export { type SerializedCodeSchema, type CodeOptions, type CodeLanguage, CodeSch
|
|
|
65
65
|
export { type ColorFormat, type ParsedColor, COLOR_FORMATS, DEFAULT_COLOR_FORMAT, } from "./schema/colorFormat.js";
|
|
66
66
|
export { type SerializedDateTimeSchema, DateTimeSchema, } from "./schema/datetime.js";
|
|
67
67
|
export { type SerializedKeyOfSchema, KeyOfSchema } from "./schema/keyOf.js";
|
|
68
|
+
export { type SerializedValViewSchema, ValViewSchema } from "./schema/view.js";
|
|
69
|
+
export { type ValViewSource, isValViewSource } from "./source/view.js";
|
|
70
|
+
export type { ValView } from "./selector/view.js";
|
|
71
|
+
export type { ValViewHandle, ResolvableModule } from "./source/viewHandle.js";
|
|
72
|
+
import { createViewHandle, isViewHandle, resolveViewedModule, viewHandleModule, viewModulesOf } from "./source/viewHandle.js";
|
|
68
73
|
export { type SerializedRouteSchema, RouteSchema } from "./schema/route.js";
|
|
69
74
|
export { type SerializedRichTextSchema, RichTextSchema, } from "./schema/richtext.js";
|
|
70
75
|
export { type SerializedDiscriminatedUnionSchema, type DiscriminatedUnionSourceOf, DiscriminatedUnionSchema, } from "./schema/discriminatedUnion.js";
|
|
@@ -85,6 +90,11 @@ declare const Internal: {
|
|
|
85
90
|
VERSION: {
|
|
86
91
|
core: string;
|
|
87
92
|
};
|
|
93
|
+
createViewHandle: typeof createViewHandle;
|
|
94
|
+
isViewHandle: typeof isViewHandle;
|
|
95
|
+
resolveViewedModule: typeof resolveViewedModule;
|
|
96
|
+
viewHandleModule: typeof viewHandleModule;
|
|
97
|
+
viewModulesOf: typeof viewModulesOf;
|
|
88
98
|
mediaUrl: typeof mediaUrl;
|
|
89
99
|
resolveMedia: typeof resolveMedia;
|
|
90
100
|
isRemoteMediaPath: typeof isRemoteMediaPath;
|
|
@@ -10,6 +10,7 @@ import { richtext } from "./schema/richtext.js";
|
|
|
10
10
|
import { image } from "./schema/image.js";
|
|
11
11
|
import { literal } from "./schema/literal.js";
|
|
12
12
|
import { keyOf } from "./schema/keyOf.js";
|
|
13
|
+
import { view } from "./schema/view.js";
|
|
13
14
|
import { record } from "./schema/record.js";
|
|
14
15
|
import { file } from "./schema/file.js";
|
|
15
16
|
import { fileset } from "./schema/fileset.js";
|
|
@@ -175,6 +176,23 @@ export type InitSchema = {
|
|
|
175
176
|
*
|
|
176
177
|
*/
|
|
177
178
|
readonly keyOf: typeof keyOf;
|
|
179
|
+
/**
|
|
180
|
+
* Show another module as part of this one, in the Val editor only.
|
|
181
|
+
*
|
|
182
|
+
* The source is a pointer — `{ view: "/other.val.ts" }` — and nothing else.
|
|
183
|
+
* The module it names keeps its own source, patches, validation and address;
|
|
184
|
+
* the editor shows a row here that navigates to it.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* import otherVal from "./other.val"; // another module
|
|
188
|
+
* const schema = s.object({ shared: s.view(otherVal), title: s.string() });
|
|
189
|
+
* export default c.define("/example.val.ts", schema, {
|
|
190
|
+
* shared: { view: "/other.val.ts" },
|
|
191
|
+
* title: "Hello",
|
|
192
|
+
* });
|
|
193
|
+
*
|
|
194
|
+
*/
|
|
195
|
+
readonly view: typeof view;
|
|
178
196
|
/**
|
|
179
197
|
* Define a record.
|
|
180
198
|
*
|
|
@@ -381,6 +399,7 @@ export declare function initSchema(): {
|
|
|
381
399
|
val?: never;
|
|
382
400
|
_type?: never;
|
|
383
401
|
_ref?: never;
|
|
402
|
+
view?: never;
|
|
384
403
|
andThen?: never;
|
|
385
404
|
assert?: never;
|
|
386
405
|
fold?: never;
|
|
@@ -397,6 +416,7 @@ export declare function initSchema(): {
|
|
|
397
416
|
image: typeof image;
|
|
398
417
|
literal: <T extends string>(value: T) => import("./index.js").LiteralSchema<T>;
|
|
399
418
|
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>;
|
|
419
|
+
view: <M extends import("./selector/index.js").GenericSelector<import("./source/index.js").Source> & import("./module.js").ValModuleBrand>(valModule: M) => import("./index.js").ValViewSchema<import("./module.js").ModuleIdOf<M>, M extends import("./selector/index.js").GenericSelector<infer S extends import("./source/index.js").Source, undefined> ? S : never>;
|
|
400
420
|
record: typeof record;
|
|
401
421
|
file: typeof file;
|
|
402
422
|
fileset: (options: import("./schema/fileset.js").FilesetOptions) => import("./index.js").RecordSchema<import("./index.js").ObjectSchema<{
|
|
@@ -6,10 +6,27 @@ import { ModuleFilePath, ModulePath, SourcePath } from "./val/index.js";
|
|
|
6
6
|
import { Json } from "./Json.js";
|
|
7
7
|
import { RawString } from "./schema/string.js";
|
|
8
8
|
declare const brand: unique symbol;
|
|
9
|
-
|
|
10
|
-
export type
|
|
9
|
+
declare const idBrand: unique symbol;
|
|
10
|
+
export type ValModule<T extends SelectorSource, Id extends string = string> = SelectorOf<T> & ValModuleBrand<Id>;
|
|
11
|
+
/**
|
|
12
|
+
* The brand that says "this is a module", carrying the module's own id.
|
|
13
|
+
*
|
|
14
|
+
* `Id` is inferred from {@link define}'s first argument, so a module's PATH is
|
|
15
|
+
* in its type. That is what lets `s.view(fooVal)` produce a schema whose source
|
|
16
|
+
* type is the literal `{ view: "/foo.val.ts" }` — the path autocompletes at the
|
|
17
|
+
* call site, and a source that names a different module than the schema does is
|
|
18
|
+
* a type error rather than something validation has to catch later.
|
|
19
|
+
*
|
|
20
|
+
* Defaulted to `string` so every `ValModule<T>` written without it keeps
|
|
21
|
+
* meaning what it did. The property is covariant, so a module with a known id
|
|
22
|
+
* is still assignable everywhere `ValModule<T>` is expected.
|
|
23
|
+
*/
|
|
24
|
+
export type ValModuleBrand<Id extends string = string> = {
|
|
11
25
|
[brand]: "ValModule";
|
|
26
|
+
[idBrand]: Id;
|
|
12
27
|
};
|
|
28
|
+
/** A module's own path, read back off its type. */
|
|
29
|
+
export type ModuleIdOf<M> = M extends ValModuleBrand<infer Id> ? Id : string;
|
|
13
30
|
export type InferValModuleType<T extends ValModule<SelectorSource>> = T extends GenericSelector<infer S> ? S : never;
|
|
14
31
|
export type ReplaceRawStringWithString<T extends SelectorSource> = SelectorSource extends T ? T : T extends RawString ? string : T extends {
|
|
15
32
|
[key in string]: SelectorSource;
|
|
@@ -32,8 +49,8 @@ export type ReplaceRawStringWithString<T extends SelectorSource> = SelectorSourc
|
|
|
32
49
|
* external at all.
|
|
33
50
|
*/
|
|
34
51
|
type InlineEntriesFor<S> = S extends ExternalRecordSrc<infer Item, string, boolean> ? Record<string, Item> : never;
|
|
35
|
-
export declare function define<T extends Schema<SelectorSource
|
|
36
|
-
schema: T, source: ReplaceRawStringWithString<SelectorOfSchema<T>> | InlineEntriesFor<SelectorOfSchema<T>>): ValModule<SelectorOfSchema<T
|
|
52
|
+
export declare function define<T extends Schema<SelectorSource>, Id extends string>(id: Id, // TODO: `/${string}`
|
|
53
|
+
schema: T, source: ReplaceRawStringWithString<SelectorOfSchema<T>> | InlineEntriesFor<SelectorOfSchema<T>>): ValModule<SelectorOfSchema<T>, Id>;
|
|
37
54
|
export declare function getSource<T extends Source>(valModule: ValModule<T>): T;
|
|
38
55
|
/**
|
|
39
56
|
* Whether `value` is a Val module, i.e. something {@link define} produced.
|
|
@@ -2,6 +2,32 @@ import { Schema } from "./schema/index.js";
|
|
|
2
2
|
import { SelectorSource } from "./selector/index.js";
|
|
3
3
|
import { ImageSource } from "./source/media.js";
|
|
4
4
|
import { ModuleFilePath, SourcePath } from "./val/index.js";
|
|
5
|
+
/**
|
|
6
|
+
* THE RULE, and the only sentence that needs to be remembered:
|
|
7
|
+
*
|
|
8
|
+
* **`.describe()` is INPUT HELP and is shown wherever that field — or a
|
|
9
|
+
* record's key — is being ENTERED; `.preview()` is a NAME and is shown
|
|
10
|
+
* wherever the value is REFERRED TO rather than edited; `.render()` is
|
|
11
|
+
* LAYOUT and applies only while the field is open in front of you.**
|
|
12
|
+
*
|
|
13
|
+
* The test that settles every case: **can the reader change something here?**
|
|
14
|
+
* If yes it is a place for a description — the input beside a label, the key
|
|
15
|
+
* box in "New entry", "Rename key", "Duplicate", "New page", the key half of a
|
|
16
|
+
* reference dropdown. If no, it is a place for a preview — a list row, a
|
|
17
|
+
* reference once chosen, a search hit, a sitemap row, the heading of what you
|
|
18
|
+
* navigated to.
|
|
19
|
+
*
|
|
20
|
+
* That is why a description is plain data on the serialized schema and a
|
|
21
|
+
* preview is a closure: a description is true before any value exists and says
|
|
22
|
+
* the same thing to everyone filling the field in, and a preview cannot exist
|
|
23
|
+
* without the one value it names. So a description must never be used as a
|
|
24
|
+
* subtitle — it would repeat one sentence under every row of a list — and a
|
|
25
|
+
* preview must never be used as help text, because there is nothing to preview
|
|
26
|
+
* until after the value has been entered.
|
|
27
|
+
*
|
|
28
|
+
* None of the three substitutes for another: a field with a perfect
|
|
29
|
+
* description still previews as `#3` until someone writes the preview.
|
|
30
|
+
*/
|
|
5
31
|
/**
|
|
6
32
|
* A PREVIEW is how a VALUE is shown wherever a preview of it is needed — a row
|
|
7
33
|
* in a sortable list, a key in a reference dropdown, a search hit, a
|
|
@@ -76,14 +102,12 @@ export type ArrayPreview = {
|
|
|
76
102
|
items: [index: number, value: PreviewItem][];
|
|
77
103
|
};
|
|
78
104
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
* on purpose - how a preview is laid out is the editor's business, not the
|
|
84
|
-
* schema's.
|
|
105
|
+
* How a CONTAINER's children preview. `parent` is the discriminant, and every
|
|
106
|
+
* consumer narrows on it rather than on the union as a whole. There is no
|
|
107
|
+
* `layout` here on purpose - how a preview is laid out is the editor's
|
|
108
|
+
* business, not the schema's.
|
|
85
109
|
*/
|
|
86
|
-
type
|
|
110
|
+
export type PreviewRows = RecordPreview | ArrayPreview;
|
|
87
111
|
type WithStatus<T> = {
|
|
88
112
|
status: "error";
|
|
89
113
|
message: string;
|
|
@@ -94,7 +118,40 @@ type WithStatus<T> = {
|
|
|
94
118
|
status: "success";
|
|
95
119
|
data: T;
|
|
96
120
|
};
|
|
97
|
-
|
|
121
|
+
/**
|
|
122
|
+
* Everything known about ONE path, which is two independent things.
|
|
123
|
+
*
|
|
124
|
+
* They are separate fields rather than a union because a path can have both,
|
|
125
|
+
* and that is not a corner case: `s.array(section).preview(...)` where
|
|
126
|
+
* `section` also previews is a section list that shows its rows AND names
|
|
127
|
+
* itself when it is nested in something. One of them would have had to win.
|
|
128
|
+
*
|
|
129
|
+
* - {@link self} - what THIS value is called, from its own schema's
|
|
130
|
+
* `.preview(...)`. This is the only answer for a value with no container to
|
|
131
|
+
* ask: a module root (whose `.preview` was never run before this existed) and
|
|
132
|
+
* any field of an object.
|
|
133
|
+
* - {@link rows} - what its CHILDREN are called, from the ITEM schema's
|
|
134
|
+
* `.preview(...)`, reified by the container. Kept alongside `self` rather
|
|
135
|
+
* than derived from the children's `self` entries because a list needs every
|
|
136
|
+
* row in one answer, and because an EMPTY list still has to preview as an
|
|
137
|
+
* empty list - which is a fact about the schema, not about any value.
|
|
138
|
+
*/
|
|
139
|
+
export type PreviewNode = {
|
|
140
|
+
self?: PreviewItem;
|
|
141
|
+
rows?: PreviewRows;
|
|
142
|
+
};
|
|
143
|
+
export type ReifiedPreview = Record<SourcePath | ModuleFilePath, WithStatus<PreviewNode>>;
|
|
144
|
+
/**
|
|
145
|
+
* Fold `incoming` into `target`, path by path. MUTATES `target`.
|
|
146
|
+
*
|
|
147
|
+
* Every `executePreview` used to copy child results across with a plain
|
|
148
|
+
* assignment, which was fine while one path held one thing. It no longer does:
|
|
149
|
+
* a container writes `rows` at its own path and its `self` arrives from the
|
|
150
|
+
* same walk, so an overwrite silently drops whichever landed first. Merging is
|
|
151
|
+
* per FIELD, and an error at a path beats any data there - a preview that threw
|
|
152
|
+
* must not be reported as a preview that is merely absent.
|
|
153
|
+
*/
|
|
154
|
+
export declare function mergePreviewInto(target: ReifiedPreview, incoming: ReifiedPreview): void;
|
|
98
155
|
/**
|
|
99
156
|
* Which paths a preview is being computed FOR.
|
|
100
157
|
*
|
|
@@ -1,4 +1,30 @@
|
|
|
1
1
|
import type { SerializedSchema } from "./schema/index.js";
|
|
2
|
+
/**
|
|
3
|
+
* THE RULE, and the only sentence that needs to be remembered:
|
|
4
|
+
*
|
|
5
|
+
* **`.describe()` is INPUT HELP and is shown wherever that field — or a
|
|
6
|
+
* record's key — is being ENTERED; `.preview()` is a NAME and is shown
|
|
7
|
+
* wherever the value is REFERRED TO rather than edited; `.render()` is
|
|
8
|
+
* LAYOUT and applies only while the field is open in front of you.**
|
|
9
|
+
*
|
|
10
|
+
* The test that settles every case: **can the reader change something here?**
|
|
11
|
+
* If yes it is a place for a description — the input beside a label, the key
|
|
12
|
+
* box in "New entry", "Rename key", "Duplicate", "New page", the key half of a
|
|
13
|
+
* reference dropdown. If no, it is a place for a preview — a list row, a
|
|
14
|
+
* reference once chosen, a search hit, a sitemap row, the heading of what you
|
|
15
|
+
* navigated to.
|
|
16
|
+
*
|
|
17
|
+
* That is why a description is plain data on the serialized schema and a
|
|
18
|
+
* preview is a closure: a description is true before any value exists and says
|
|
19
|
+
* the same thing to everyone filling the field in, and a preview cannot exist
|
|
20
|
+
* without the one value it names. So a description must never be used as a
|
|
21
|
+
* subtitle — it would repeat one sentence under every row of a list — and a
|
|
22
|
+
* preview must never be used as help text, because there is nothing to preview
|
|
23
|
+
* until after the value has been entered.
|
|
24
|
+
*
|
|
25
|
+
* None of the three substitutes for another: a field with a perfect
|
|
26
|
+
* description still previews as `#3` until someone writes the preview.
|
|
27
|
+
*/
|
|
2
28
|
/**
|
|
3
29
|
* A RENDER is how the FIELD ITSELF is laid out in the editor, and it applies
|
|
4
30
|
* only when you are looking at the field: `.render({ as: "inline" })` on an
|
|
@@ -41,10 +41,13 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
|
|
|
41
41
|
/**
|
|
42
42
|
* Describe this field.
|
|
43
43
|
*
|
|
44
|
-
* The description is shown
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
44
|
+
* The description is INPUT HELP: it is shown where this field's value is
|
|
45
|
+
* entered — beside its input in the Val editor, and for a record's key
|
|
46
|
+
* schema in every form that asks for a key — so it is where you say what an
|
|
47
|
+
* editor needs to know to fill it in RIGHT, which the field name cannot
|
|
48
|
+
* carry. It is not a name for the value: that is `.preview(...)`, and it is
|
|
49
|
+
* read somewhere else. The description also travels in the serialized
|
|
50
|
+
* schema, which is what the AI assistant and the MCP tools read.
|
|
48
51
|
*
|
|
49
52
|
* Pass `null` to clear a description set earlier.
|
|
50
53
|
*
|
|
@@ -91,7 +94,7 @@ export declare class ArraySchema<T extends Schema<SelectorSource>, Src extends S
|
|
|
91
94
|
}[];
|
|
92
95
|
protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
|
|
93
96
|
protected executeSerialize(): SerializedArraySchema;
|
|
94
|
-
protected executePreview(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: PreviewScope): ReifiedPreview;
|
|
97
|
+
protected executePreview(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: PreviewScope, selfIsReifiedByParent?: boolean): ReifiedPreview;
|
|
95
98
|
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
96
99
|
protected declaresItemPreview(): boolean;
|
|
97
100
|
/**
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
-
import { ItemPreviewInput, PreviewItem
|
|
2
|
+
import { ItemPreviewInput, PreviewItem } from "../preview.js";
|
|
3
3
|
import { FieldRender } from "../render.js";
|
|
4
4
|
import { SourcePath } from "../val/index.js";
|
|
5
5
|
import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
|
|
@@ -27,10 +27,13 @@ export declare class BooleanSchema<Src extends boolean | null> extends Schema<Sr
|
|
|
27
27
|
/**
|
|
28
28
|
* Describe this field.
|
|
29
29
|
*
|
|
30
|
-
* The description is shown
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
30
|
+
* The description is INPUT HELP: it is shown where this field's value is
|
|
31
|
+
* entered — beside its input in the Val editor, and for a record's key
|
|
32
|
+
* schema in every form that asks for a key — so it is where you say what an
|
|
33
|
+
* editor needs to know to fill it in RIGHT, which the field name cannot
|
|
34
|
+
* carry. It is not a name for the value: that is `.preview(...)`, and it is
|
|
35
|
+
* read somewhere else. The description also travels in the serialized
|
|
36
|
+
* schema, which is what the AI assistant and the MCP tools read.
|
|
34
37
|
*
|
|
35
38
|
* Pass `null` to clear a description set earlier.
|
|
36
39
|
*
|
|
@@ -93,6 +96,5 @@ export declare class BooleanSchema<Src extends boolean | null> extends Schema<Sr
|
|
|
93
96
|
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
94
97
|
protected declaresItemPreview(): boolean;
|
|
95
98
|
protected executeSerialize(): SerializedSchema;
|
|
96
|
-
protected executePreview(): ReifiedPreview;
|
|
97
99
|
}
|
|
98
100
|
export declare const boolean: () => BooleanSchema<boolean>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
-
import { ItemPreviewInput, PreviewItem
|
|
2
|
+
import { ItemPreviewInput, PreviewItem } from "../preview.js";
|
|
3
3
|
import { FieldRender } from "../render.js";
|
|
4
4
|
import { SourcePath } from "../val/index.js";
|
|
5
5
|
import { RawString } from "./string.js";
|
|
@@ -58,10 +58,13 @@ export declare class CodeSchema<Src extends string | null> extends Schema<Src> {
|
|
|
58
58
|
/**
|
|
59
59
|
* Describe this field.
|
|
60
60
|
*
|
|
61
|
-
* The description is shown
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
61
|
+
* The description is INPUT HELP: it is shown where this field's value is
|
|
62
|
+
* entered — beside its input in the Val editor, and for a record's key
|
|
63
|
+
* schema in every form that asks for a key — so it is where you say what an
|
|
64
|
+
* editor needs to know to fill it in RIGHT, which the field name cannot
|
|
65
|
+
* carry. It is not a name for the value: that is `.preview(...)`, and it is
|
|
66
|
+
* read somewhere else. The description also travels in the serialized
|
|
67
|
+
* schema, which is what the AI assistant and the MCP tools read.
|
|
65
68
|
*
|
|
66
69
|
* Pass `null` to clear a description set earlier.
|
|
67
70
|
*
|
|
@@ -143,6 +146,5 @@ export declare class CodeSchema<Src extends string | null> extends Schema<Src> {
|
|
|
143
146
|
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
144
147
|
protected declaresItemPreview(): boolean;
|
|
145
148
|
protected executeSerialize(): SerializedSchema;
|
|
146
|
-
protected executePreview(): ReifiedPreview;
|
|
147
149
|
}
|
|
148
150
|
export declare const code: (options?: CodeOptions) => CodeSchema<RawString>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
-
import { ItemPreviewInput, PreviewItem
|
|
2
|
+
import { ItemPreviewInput, PreviewItem } from "../preview.js";
|
|
3
3
|
import { FieldRender } from "../render.js";
|
|
4
4
|
import { SourcePath } from "../val/index.js";
|
|
5
5
|
import { ColorFormat } from "./colorFormat.js";
|
|
@@ -53,10 +53,13 @@ export declare class ColorSchema<Src extends string | null> extends Schema<Src>
|
|
|
53
53
|
/**
|
|
54
54
|
* Describe this field.
|
|
55
55
|
*
|
|
56
|
-
* The description is shown
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
*
|
|
56
|
+
* The description is INPUT HELP: it is shown where this field's value is
|
|
57
|
+
* entered — beside its input in the Val editor, and for a record's key
|
|
58
|
+
* schema in every form that asks for a key — so it is where you say what an
|
|
59
|
+
* editor needs to know to fill it in RIGHT, which the field name cannot
|
|
60
|
+
* carry. It is not a name for the value: that is `.preview(...)`, and it is
|
|
61
|
+
* read somewhere else. The description also travels in the serialized
|
|
62
|
+
* schema, which is what the AI assistant and the MCP tools read.
|
|
60
63
|
*
|
|
61
64
|
* Pass `null` to clear a description set earlier.
|
|
62
65
|
*
|
|
@@ -129,6 +132,5 @@ export declare class ColorSchema<Src extends string | null> extends Schema<Src>
|
|
|
129
132
|
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
130
133
|
protected declaresItemPreview(): boolean;
|
|
131
134
|
protected executeSerialize(): SerializedSchema;
|
|
132
|
-
protected executePreview(): ReifiedPreview;
|
|
133
135
|
}
|
|
134
136
|
export declare const color: (options?: ColorOptions) => ColorSchema<RawString>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
-
import { ItemPreviewInput, PreviewItem
|
|
2
|
+
import { ItemPreviewInput, PreviewItem } from "../preview.js";
|
|
3
3
|
import { FieldRender } from "../render.js";
|
|
4
4
|
import { SourcePath } from "../val/index.js";
|
|
5
5
|
import { RawString } from "./string.js";
|
|
@@ -46,10 +46,13 @@ export declare class DateSchema<Src extends string | null> extends Schema<Src> {
|
|
|
46
46
|
/**
|
|
47
47
|
* Describe this field.
|
|
48
48
|
*
|
|
49
|
-
* The description is shown
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
49
|
+
* The description is INPUT HELP: it is shown where this field's value is
|
|
50
|
+
* entered — beside its input in the Val editor, and for a record's key
|
|
51
|
+
* schema in every form that asks for a key — so it is where you say what an
|
|
52
|
+
* editor needs to know to fill it in RIGHT, which the field name cannot
|
|
53
|
+
* carry. It is not a name for the value: that is `.preview(...)`, and it is
|
|
54
|
+
* read somewhere else. The description also travels in the serialized
|
|
55
|
+
* schema, which is what the AI assistant and the MCP tools read.
|
|
53
56
|
*
|
|
54
57
|
* Pass `null` to clear a description set earlier.
|
|
55
58
|
*
|
|
@@ -132,7 +135,6 @@ export declare class DateSchema<Src extends string | null> extends Schema<Src> {
|
|
|
132
135
|
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
133
136
|
protected declaresItemPreview(): boolean;
|
|
134
137
|
protected executeSerialize(): SerializedSchema;
|
|
135
|
-
protected executePreview(): ReifiedPreview;
|
|
136
138
|
}
|
|
137
139
|
export declare const date: (options?: Record<string, never>) => DateSchema<RawString>;
|
|
138
140
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CustomValidateFunction, Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
-
import { ItemPreviewInput, PreviewItem
|
|
2
|
+
import { ItemPreviewInput, PreviewItem } from "../preview.js";
|
|
3
3
|
import { FieldRender } from "../render.js";
|
|
4
4
|
import { SourcePath } from "../val/index.js";
|
|
5
5
|
import { RawString } from "./string.js";
|
|
@@ -50,10 +50,13 @@ export declare class DateTimeSchema<Src extends string | null> extends Schema<Sr
|
|
|
50
50
|
/**
|
|
51
51
|
* Describe this field.
|
|
52
52
|
*
|
|
53
|
-
* The description is shown
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
53
|
+
* The description is INPUT HELP: it is shown where this field's value is
|
|
54
|
+
* entered — beside its input in the Val editor, and for a record's key
|
|
55
|
+
* schema in every form that asks for a key — so it is where you say what an
|
|
56
|
+
* editor needs to know to fill it in RIGHT, which the field name cannot
|
|
57
|
+
* carry. It is not a name for the value: that is `.preview(...)`, and it is
|
|
58
|
+
* read somewhere else. The description also travels in the serialized
|
|
59
|
+
* schema, which is what the AI assistant and the MCP tools read.
|
|
57
60
|
*
|
|
58
61
|
* Pass `null` to clear a description set earlier.
|
|
59
62
|
*
|
|
@@ -159,7 +162,6 @@ export declare class DateTimeSchema<Src extends string | null> extends Schema<Sr
|
|
|
159
162
|
protected executePreviewItem(src: NonNullable<Src>): PreviewItem | null;
|
|
160
163
|
protected declaresItemPreview(): boolean;
|
|
161
164
|
protected executeSerialize(): SerializedSchema;
|
|
162
|
-
protected executePreview(): ReifiedPreview;
|
|
163
165
|
}
|
|
164
166
|
export declare const datetime: (options?: Record<string, never>) => DateTimeSchema<RawString>;
|
|
165
167
|
export {};
|