@valbuild/core 0.130.0 → 0.136.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 +119 -0
- package/dist/declarations/src/index.d.ts +24 -0
- package/dist/declarations/src/initSchema.d.ts +20 -0
- package/dist/declarations/src/module.d.ts +21 -4
- package/dist/declarations/src/schema/index.d.ts +2 -1
- package/dist/declarations/src/schema/object.d.ts +10 -0
- 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/view.d.ts +41 -0
- package/dist/declarations/src/source/viewHandle.d.ts +69 -0
- package/dist/{index-71a59a88.esm.js → index-84fa4a70.esm.js} +706 -6
- package/dist/{index-27d57011.cjs.dev.js → index-8a6c2682.cjs.dev.js} +708 -5
- package/dist/{index-9e372bd7.cjs.prod.js → index-e4035f4d.cjs.prod.js} +708 -5
- package/dist/valbuild-core.cjs.dev.js +4 -1
- package/dist/valbuild-core.cjs.prod.js +4 -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,124 @@
|
|
|
1
1
|
# @valbuild/core
|
|
2
2
|
|
|
3
|
+
## 0.136.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#713](https://github.com/valbuild/val/pull/713) [`f3a4bb7`](https://github.com/valbuild/val/commit/f3a4bb7aea604943b92545c122243798c759715c) Thanks [@freekh](https://github.com/freekh)! - The Studio can load a bundler, and it stops telling a managed project that its
|
|
8
|
+
publish is on its way out.
|
|
9
|
+
|
|
10
|
+
**A managed project is one with no repository and no host watching one.** There
|
|
11
|
+
is nothing outside the browser to pick a commit up, so the Studio has been
|
|
12
|
+
showing it a story that belongs to a project with a repository: a `Building`
|
|
13
|
+
spinner, waiting for an event that can never arrive. It does not resolve on a
|
|
14
|
+
reload, on a retry, or tomorrow, and there is no way to tell it from a deploy
|
|
15
|
+
that is merely slow.
|
|
16
|
+
|
|
17
|
+
The content service now reports which kind of project this is, as `sourceMode`
|
|
18
|
+
on `/stat`, and the Studio narrates accordingly:
|
|
19
|
+
|
|
20
|
+
- a **connected** project keeps the deploy feed and keeps `Building`, because
|
|
21
|
+
there a host genuinely does pick the commit up;
|
|
22
|
+
- a **managed** one says `Saved, not yet live` instead — a durable condition,
|
|
23
|
+
not a phase, because nothing else will ever resolve it.
|
|
24
|
+
|
|
25
|
+
A server that reports no source mode keeps the behaviour it has today. Absence
|
|
26
|
+
means "not reported", never "managed": `fs` mode has no project to have a mode,
|
|
27
|
+
and neither does a content service that predates the field.
|
|
28
|
+
|
|
29
|
+
**The Studio also loads `@valbuild/tanstack-build` in the tab**, at mount and at
|
|
30
|
+
idle, for the managed projects that will need it — a browser that is the
|
|
31
|
+
deployer needs a bundler. This is the groundwork for browser-side publishing;
|
|
32
|
+
the publish path itself is unchanged in this release.
|
|
33
|
+
|
|
34
|
+
That bundler is `@rolldown/browser`, whose WebAssembly binary is 10.9 MB and is
|
|
35
|
+
**not** in the npm package. It is served from `static.val.build`, addressed by
|
|
36
|
+
the SHA-256 of its own bytes, so the build and the binary it needs cannot drift
|
|
37
|
+
apart. A deployment that must not reach that host — an air-gapped install, a
|
|
38
|
+
mirror — sets `globalThis.__VAL_ROLLDOWN_WASM_URL__` before the Studio loads and
|
|
39
|
+
needs no rebuild.
|
|
40
|
+
|
|
41
|
+
**Building in the browser requires a cross-origin isolated page.** Rolldown runs
|
|
42
|
+
WebAssembly on worker threads that share memory, and a browser will not hand a
|
|
43
|
+
`SharedArrayBuffer` to a worker otherwise. Without
|
|
44
|
+
`Cross-Origin-Opener-Policy: same-origin` and
|
|
45
|
+
`Cross-Origin-Embedder-Policy: require-corp` on the document Val is mounted in,
|
|
46
|
+
loading the bundler now fails with a message that says exactly that, instead of
|
|
47
|
+
a `DataCloneError` thrown from inside a worker. Nothing else in this release is
|
|
48
|
+
affected: a Studio that never builds in the browser never asks.
|
|
49
|
+
|
|
50
|
+
## 0.134.0
|
|
51
|
+
|
|
52
|
+
### Minor Changes
|
|
53
|
+
|
|
54
|
+
- [#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`.
|
|
55
|
+
|
|
56
|
+
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:
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
const page = useVal(pageVal);
|
|
60
|
+
const header = useVal(page.header); // the header module's content, typed
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
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.
|
|
64
|
+
|
|
65
|
+
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:
|
|
66
|
+
|
|
67
|
+
```tsx
|
|
68
|
+
const page = useVal(pageVal);
|
|
69
|
+
const note = useValRoute(page.notes, params); // the router module the view names
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Works in draft mode, including when the page itself has a pending edit. Two things to know:
|
|
73
|
+
|
|
74
|
+
- **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.
|
|
75
|
+
- **`ValView<Source>` is now a member of `SelectorSource`**, since a reader accepts one.
|
|
76
|
+
|
|
77
|
+
- [#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.
|
|
78
|
+
|
|
79
|
+
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:
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
import employeesVal from "../data/employees.val";
|
|
83
|
+
|
|
84
|
+
const schema = s.object({
|
|
85
|
+
title: s.string(),
|
|
86
|
+
employees: s.view(employeesVal),
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
export default c.define("/app/menneskene/page.val.ts", schema, {
|
|
90
|
+
title: "Våre folk",
|
|
91
|
+
employees: { view: "/data/employees.val.ts" },
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
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.
|
|
96
|
+
|
|
97
|
+
A few things worth knowing:
|
|
98
|
+
|
|
99
|
+
- **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" }`.
|
|
100
|
+
- **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.
|
|
101
|
+
- **`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.
|
|
102
|
+
- **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.
|
|
103
|
+
- **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.
|
|
104
|
+
- **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.
|
|
105
|
+
|
|
106
|
+
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:
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
// Not in the nav, and on exactly one page.
|
|
110
|
+
export default c.define("/data/employees.val.ts", s.record(...).hidden(), { ... });
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
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.
|
|
114
|
+
|
|
115
|
+
One gap worth naming rather than leaving to be discovered: a view pointing at a
|
|
116
|
+
module the project does not have is reported by the FIELD — the row says the
|
|
117
|
+
target is missing — but not by `val validate`. The schema check compares the
|
|
118
|
+
pointer against the schema, and the cycle check deliberately skips a target that
|
|
119
|
+
is not a module of the project, so neither catches it. A project-level check
|
|
120
|
+
belongs with them and is not here yet.
|
|
121
|
+
|
|
3
122
|
## 0.130.0
|
|
4
123
|
|
|
5
124
|
### Patch Changes
|
|
@@ -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";
|
|
@@ -81,10 +86,29 @@ export declare const FATAL_ERROR_TYPES: readonly ["no-schema", "no-source", "inv
|
|
|
81
86
|
export type FatalErrorType = (typeof FATAL_ERROR_TYPES)[number];
|
|
82
87
|
export declare const DEFAULT_CONTENT_HOST = "https://content.val.build";
|
|
83
88
|
export declare const DEFAULT_APP_HOST = "https://admin.val.build";
|
|
89
|
+
/**
|
|
90
|
+
* Where Val's own immutable build artifacts are served from.
|
|
91
|
+
*
|
|
92
|
+
* Only one kind of thing lives here so far: the WebAssembly binary of the
|
|
93
|
+
* bundler the Studio builds a site with. It cannot travel with the Studio's
|
|
94
|
+
* own bundle -- 10.9 MB of binary, and `/api/val/static` is a base64 record of
|
|
95
|
+
* TEXT inside the server bundle, which both corrupts it and would carry it
|
|
96
|
+
* into every project that installs `@valbuild/ui`.
|
|
97
|
+
*
|
|
98
|
+
* Everything under this host is addressed by the SHA-256 of its own bytes, so
|
|
99
|
+
* an upload is idempotent, a URL is immutable, and the artifact a build asks
|
|
100
|
+
* for cannot be a different one than the build was made against.
|
|
101
|
+
*/
|
|
102
|
+
export declare const DEFAULT_STATIC_HOST = "https://static.val.build";
|
|
84
103
|
declare const Internal: {
|
|
85
104
|
VERSION: {
|
|
86
105
|
core: string;
|
|
87
106
|
};
|
|
107
|
+
createViewHandle: typeof createViewHandle;
|
|
108
|
+
isViewHandle: typeof isViewHandle;
|
|
109
|
+
resolveViewedModule: typeof resolveViewedModule;
|
|
110
|
+
viewHandleModule: typeof viewHandleModule;
|
|
111
|
+
viewModulesOf: typeof viewModulesOf;
|
|
88
112
|
mediaUrl: typeof mediaUrl;
|
|
89
113
|
resolveMedia: typeof resolveMedia;
|
|
90
114
|
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.
|
|
@@ -5,6 +5,7 @@ import { SerializedBooleanSchema } from "./boolean.js";
|
|
|
5
5
|
import { SerializedFileSchema } from "./file.js";
|
|
6
6
|
import { SerializedImageSchema } from "./image.js";
|
|
7
7
|
import { SerializedKeyOfSchema } from "./keyOf.js";
|
|
8
|
+
import { SerializedValViewSchema } from "./view.js";
|
|
8
9
|
import { SerializedLiteralSchema } from "./literal.js";
|
|
9
10
|
import { SerializedNumberSchema } from "./number.js";
|
|
10
11
|
import { SerializedObjectSchema } from "./object.js";
|
|
@@ -24,7 +25,7 @@ import { ValidationError, ValidationErrors } from "./validation/ValidationError.
|
|
|
24
25
|
import { FileSource } from "../source/media.js";
|
|
25
26
|
import { GenericRichTextSourceNode, RichTextSource } from "../source/richtext.js";
|
|
26
27
|
import { ReifiedPreview, PreviewScope, PreviewItem } from "../preview.js";
|
|
27
|
-
export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedDiscriminatedUnionSchema | SerializedEnumSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedLocaleSchema | SerializedSettingsSchema | SerializedImageSchema;
|
|
28
|
+
export type SerializedSchema = SerializedStringSchema | SerializedLiteralSchema | SerializedBooleanSchema | SerializedNumberSchema | SerializedObjectSchema | SerializedArraySchema | SerializedDiscriminatedUnionSchema | SerializedEnumSchema | SerializedRichTextSchema | SerializedRecordSchema | SerializedKeyOfSchema | SerializedFileSchema | SerializedDateSchema | SerializedDateTimeSchema | SerializedColorSchema | SerializedCodeSchema | SerializedRouteSchema | SerializedLocaleSchema | SerializedSettingsSchema | SerializedImageSchema | SerializedValViewSchema;
|
|
28
29
|
type Primitives = number | string | boolean | null | FileSource;
|
|
29
30
|
export type AssertError = {
|
|
30
31
|
message: string;
|
|
@@ -28,6 +28,16 @@ type ObjectSchemaProps = {
|
|
|
28
28
|
_type?: never;
|
|
29
29
|
/** Cannot create object with key: _ref. It is a reserved name */
|
|
30
30
|
_ref?: never;
|
|
31
|
+
/**
|
|
32
|
+
* Cannot create object with key: view. It is a reserved name.
|
|
33
|
+
*
|
|
34
|
+
* A view field's source is `{ view: "/other.val.ts" }`, and the read side
|
|
35
|
+
* maps that shape to an opaque {@link ValView} handle. An ordinary object
|
|
36
|
+
* with a single `view: string` key is structurally identical, so it would be
|
|
37
|
+
* mapped the same way and lose every one of its fields — silently. Reserving
|
|
38
|
+
* the key is what makes the shape unambiguous.
|
|
39
|
+
*/
|
|
40
|
+
view?: never;
|
|
31
41
|
/** Cannot create object with key: andThen. It is a reserved name */
|
|
32
42
|
andThen?: never;
|
|
33
43
|
/** Cannot create object with key: assert. It is a reserved name */
|
|
@@ -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", "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"];
|
|
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", "view:check-module"];
|
|
2
2
|
export type ValidationFix = (typeof ValidationFix)[number];
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import { Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
|
|
2
|
+
import type { ModuleIdOf, ValModuleBrand } from "../module.js";
|
|
3
|
+
import { ReifiedPreview } from "../preview.js";
|
|
4
|
+
import { FieldRender } from "../render.js";
|
|
5
|
+
import { GenericSelector } from "../selector/index.js";
|
|
6
|
+
import { Source } from "../source/index.js";
|
|
7
|
+
import { ValViewSource } from "../source/view.js";
|
|
8
|
+
import { ModuleFilePath, SourcePath } from "../val/index.js";
|
|
9
|
+
import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
|
|
10
|
+
export type SerializedValViewSchema = {
|
|
11
|
+
type: "view";
|
|
12
|
+
render?: FieldRender;
|
|
13
|
+
/** Never set: a view has no value of its own to preview. Carried for shape parity. */
|
|
14
|
+
preview?: true;
|
|
15
|
+
/** Always false: a view points at a module, and `null` is not a module. */
|
|
16
|
+
opt: false;
|
|
17
|
+
/** Always false: there is no value for a custom validator to look at. */
|
|
18
|
+
customValidate?: false;
|
|
19
|
+
/** The module this field points at. */
|
|
20
|
+
moduleFilePath: ModuleFilePath;
|
|
21
|
+
readonly?: boolean;
|
|
22
|
+
hidden?: boolean;
|
|
23
|
+
description?: string;
|
|
24
|
+
};
|
|
25
|
+
/** The source type of the module a selector points at. */
|
|
26
|
+
type SourceOf<M> = M extends GenericSelector<infer S> ? S : never;
|
|
27
|
+
/**
|
|
28
|
+
* A field that points at ANOTHER module, for the Val editor's benefit.
|
|
29
|
+
*
|
|
30
|
+
* The source is `{ view: "/other.val.ts" }` — a pointer, and nothing else. The
|
|
31
|
+
* module it names keeps its own source, its own patches, its own validation and
|
|
32
|
+
* its own address; this field puts a row on THIS module's screen that an editor
|
|
33
|
+
* clicks through to it.
|
|
34
|
+
*
|
|
35
|
+
* Two type parameters, each earning its place:
|
|
36
|
+
*
|
|
37
|
+
* - `Id` — the target's module file path as a literal, so the author writes
|
|
38
|
+
* `{ view: "/other.val.ts" }` with autocomplete, and naming a different module
|
|
39
|
+
* than the schema does is a type error rather than a validation error.
|
|
40
|
+
* - `T` — the target's source type, carried on a phantom slot so the READ side
|
|
41
|
+
* can say `ValView<T>` rather than `ValView<unknown>`. Nothing reads it yet;
|
|
42
|
+
* it is here so that resolving a view through `useVal` can be added without
|
|
43
|
+
* changing what is stored.
|
|
44
|
+
*/
|
|
45
|
+
export declare class ValViewSchema<Id extends string = string, T = unknown> extends Schema<ValViewSource<Id, T>> {
|
|
46
|
+
private readonly moduleFilePath;
|
|
47
|
+
/**
|
|
48
|
+
* The module this points at, as an object.
|
|
49
|
+
*
|
|
50
|
+
* Not serialized and not part of the schema's identity — it is here so the
|
|
51
|
+
* READ path can resolve a view without a path-to-module registry, which the
|
|
52
|
+
* app does not have (`val.modules` is lazy thunks, `<ValModulesClient>` is
|
|
53
|
+
* optional). Absent on a schema that came off the wire, which is fine: the
|
|
54
|
+
* Studio navigates to a view, it does not resolve one.
|
|
55
|
+
*/
|
|
56
|
+
private readonly valModule;
|
|
57
|
+
private readonly isReadonly;
|
|
58
|
+
private readonly isHidden;
|
|
59
|
+
private readonly description?;
|
|
60
|
+
private readonly renderInput;
|
|
61
|
+
constructor(moduleFilePath: Id,
|
|
62
|
+
/**
|
|
63
|
+
* The module this points at, as an object.
|
|
64
|
+
*
|
|
65
|
+
* Not serialized and not part of the schema's identity — it is here so the
|
|
66
|
+
* READ path can resolve a view without a path-to-module registry, which the
|
|
67
|
+
* app does not have (`val.modules` is lazy thunks, `<ValModulesClient>` is
|
|
68
|
+
* optional). Absent on a schema that came off the wire, which is fine: the
|
|
69
|
+
* Studio navigates to a view, it does not resolve one.
|
|
70
|
+
*/
|
|
71
|
+
valModule?: unknown, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined, renderInput?: FieldRender | null);
|
|
72
|
+
/**
|
|
73
|
+
* Describe this field.
|
|
74
|
+
*
|
|
75
|
+
* Shown next to the field's label in the Val editor — here, the place to say
|
|
76
|
+
* WHY the other module is on this screen, since an editor who follows the row
|
|
77
|
+
* is about to change content that other pages use too.
|
|
78
|
+
*
|
|
79
|
+
* @example
|
|
80
|
+
* import otherVal from "./other.val"; // another module
|
|
81
|
+
* const schema = s.object({
|
|
82
|
+
* shared: s.view(otherVal).describe("Shared by every page"),
|
|
83
|
+
* title: s.string(),
|
|
84
|
+
* });
|
|
85
|
+
* export default c.define("/example.val.ts", schema, {
|
|
86
|
+
* shared: { view: "/other.val.ts" },
|
|
87
|
+
* title: "Hello",
|
|
88
|
+
* });
|
|
89
|
+
*/
|
|
90
|
+
describe(description: string | null): ValViewSchema<Id, T>;
|
|
91
|
+
/**
|
|
92
|
+
* The stored pointer must name the module the SCHEMA names.
|
|
93
|
+
*
|
|
94
|
+
* It cannot disagree in a `.val.ts` — the source type is the literal path, so
|
|
95
|
+
* a mismatch does not compile. It can disagree in hand-written JSON (a
|
|
96
|
+
* `.jsonValues()` entry, an external record), which is what this is for.
|
|
97
|
+
*/
|
|
98
|
+
protected executeValidate(path: SourcePath, src: ValViewSource<Id, T>): ValidationErrors;
|
|
99
|
+
protected executeCustomValidateAt(_path: SourcePath, _src: ValViewSource<Id, T>): ValidationError[];
|
|
100
|
+
protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<ValViewSource<Id, T>>;
|
|
101
|
+
/**
|
|
102
|
+
* Not available: a view points at a module, and `null` is not a module.
|
|
103
|
+
* Throws if called.
|
|
104
|
+
*
|
|
105
|
+
* @example
|
|
106
|
+
* import otherVal from "./other.val"; // another module
|
|
107
|
+
* // `s.view(otherVal).nullable()` throws — a view is never nullable.
|
|
108
|
+
* const schema = s.object({ shared: s.view(otherVal), title: s.string() });
|
|
109
|
+
* export default c.define("/example.val.ts", schema, {
|
|
110
|
+
* shared: { view: "/other.val.ts" },
|
|
111
|
+
* title: "Hello",
|
|
112
|
+
* });
|
|
113
|
+
*/
|
|
114
|
+
nullable(): Schema<ValViewSource<Id, T> | null>;
|
|
115
|
+
/**
|
|
116
|
+
* Marks the row read-only.
|
|
117
|
+
*
|
|
118
|
+
* The view's OWN flag, and it says nothing about the module it names: that
|
|
119
|
+
* module has its own `readonly`, which is what governs editing once an
|
|
120
|
+
* editor is there. A view is a link rather than an editor, so this changes
|
|
121
|
+
* nothing about the row today — it exists because every schema has it, and
|
|
122
|
+
* because a view that renders its target inline would need it.
|
|
123
|
+
*
|
|
124
|
+
* @example
|
|
125
|
+
* import otherVal from "./other.val"; // another module
|
|
126
|
+
* const schema = s.object({
|
|
127
|
+
* shared: s.view(otherVal).readonly(),
|
|
128
|
+
* title: s.string(),
|
|
129
|
+
* });
|
|
130
|
+
* export default c.define("/example.val.ts", schema, {
|
|
131
|
+
* shared: { view: "/other.val.ts" },
|
|
132
|
+
* title: "Hello",
|
|
133
|
+
* });
|
|
134
|
+
*/
|
|
135
|
+
readonly(isReadonly?: boolean): ValViewSchema<Id, T>;
|
|
136
|
+
/**
|
|
137
|
+
* Hide the ROW in the editor.
|
|
138
|
+
*
|
|
139
|
+
* The view's OWN flag, and it is the only thing that decides whether the row
|
|
140
|
+
* is drawn — a view whose TARGET is hidden is still shown, and still leads
|
|
141
|
+
* there. That is the point of it: a module that many modules `keyOf` into is
|
|
142
|
+
* noise in the nav and belongs on exactly one page, so it is hidden from the
|
|
143
|
+
* listing and reached through the view that puts it where it belongs.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* import otherVal from "./other.val"; // another module
|
|
147
|
+
* const schema = s.object({
|
|
148
|
+
* shared: s.view(otherVal).hidden(),
|
|
149
|
+
* title: s.string(),
|
|
150
|
+
* });
|
|
151
|
+
* export default c.define("/example.val.ts", schema, {
|
|
152
|
+
* shared: { view: "/other.val.ts" },
|
|
153
|
+
* title: "Hello",
|
|
154
|
+
* });
|
|
155
|
+
*/
|
|
156
|
+
hidden(isHidden?: boolean): ValViewSchema<Id, T>;
|
|
157
|
+
/**
|
|
158
|
+
* How this field is laid out in the editor. Static configuration, not a
|
|
159
|
+
* callback — see `render.ts`.
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* import otherVal from "./other.val"; // another module
|
|
163
|
+
* const schema = s.object({
|
|
164
|
+
* shared: s.view(otherVal).render({ as: "inline" }),
|
|
165
|
+
* title: s.string(),
|
|
166
|
+
* });
|
|
167
|
+
* export default c.define("/example.val.ts", schema, {
|
|
168
|
+
* shared: { view: "/other.val.ts" },
|
|
169
|
+
* title: "Hello",
|
|
170
|
+
* });
|
|
171
|
+
*/
|
|
172
|
+
render(input: FieldRender): ValViewSchema<Id, T>;
|
|
173
|
+
protected executeSerialize(): SerializedSchema;
|
|
174
|
+
protected executePreview(): ReifiedPreview;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Point at another module, so the Val editor shows a way into it from here.
|
|
178
|
+
*
|
|
179
|
+
* A view stores a pointer and nothing else: the module it names keeps its own
|
|
180
|
+
* source, patches, validation and address, and an editor reaches it by clicking
|
|
181
|
+
* through rather than by hunting for it. It is there so content that has to live
|
|
182
|
+
* in its own module — a `keyOf` target, shared settings, a route-keyed record —
|
|
183
|
+
* can still be found from the page it belongs to.
|
|
184
|
+
*
|
|
185
|
+
* Reading it in consuming code gives you a pointer with no fields on it. Read
|
|
186
|
+
* the module it names directly instead.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* import otherVal from "./other.val"; // another module
|
|
190
|
+
* const schema = s.object({ shared: s.view(otherVal), title: s.string() });
|
|
191
|
+
* export default c.define("/example.val.ts", schema, {
|
|
192
|
+
* shared: { view: "/other.val.ts" },
|
|
193
|
+
* title: "Hello",
|
|
194
|
+
* });
|
|
195
|
+
*/
|
|
196
|
+
export declare const view: <M extends GenericSelector<Source> & ValModuleBrand>(valModule: M) => ValViewSchema<ModuleIdOf<M>, SourceOf<M>>;
|
|
197
|
+
export {};
|
|
@@ -13,11 +13,13 @@ import { AllRichTextOptions, RichTextSource } from "../source/richtext.js";
|
|
|
13
13
|
import { RichTextSelector } from "./richtext.js";
|
|
14
14
|
import { JsonSource } from "../source/json.js";
|
|
15
15
|
import { ExternalRecordSrc } from "../source/external.js";
|
|
16
|
+
import { ValViewSource } from "../source/view.js";
|
|
17
|
+
import { ValView } from "./view.js";
|
|
16
18
|
import { SettingsSource } from "../source/settings.js";
|
|
17
|
-
export type Selector<T extends Source> = Source extends T ? GenericSelector<T> : T extends MediaSource ? GenericSelector<T> : T extends JsonSource ? GenericSelector<JsonSource> : T extends ExternalRecordSrc ? GenericSelector<T> : T extends RichTextSource<infer O> ? RichTextSelector<O> : T extends SourceObject ? ObjectSelector<T> : T extends SourceArray ? ArraySelector<T> : T extends SettingsSource ? GenericSelector<T> : T extends string ? StringSelector<T> : T extends number ? NumberSelector<T> : T extends boolean ? BooleanSelector<T> : T extends null ? PrimitiveSelector<null> : never;
|
|
19
|
+
export type Selector<T extends Source> = Source extends T ? GenericSelector<T> : T extends MediaSource ? GenericSelector<T> : T extends JsonSource ? GenericSelector<JsonSource> : T extends ExternalRecordSrc ? GenericSelector<T> : T extends ValViewSource<string, infer Target> ? ValView<Target> : T extends RichTextSource<infer O> ? RichTextSelector<O> : T extends SourceObject ? ObjectSelector<T> : T extends SourceArray ? ArraySelector<T> : T extends SettingsSource ? GenericSelector<T> : T extends string ? StringSelector<T> : T extends number ? NumberSelector<T> : T extends boolean ? BooleanSelector<T> : T extends null ? PrimitiveSelector<null> : never;
|
|
18
20
|
export type SelectorSource = SourcePrimitive | undefined | readonly SelectorSource[] | {
|
|
19
21
|
[key: string]: SelectorSource;
|
|
20
|
-
} | MediaSource | JsonSource | ExternalRecordSrc | SettingsSource | RichTextSource<AllRichTextOptions> | GenericSelector<Source>;
|
|
22
|
+
} | MediaSource | JsonSource | ExternalRecordSrc | ValViewSource | ValView<Source> | SettingsSource | RichTextSource<AllRichTextOptions> | GenericSelector<Source>;
|
|
21
23
|
/**
|
|
22
24
|
* @internal
|
|
23
25
|
*/
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level-only slot naming the source type of the module a {@link ValView}
|
|
3
|
+
* points at. Required, not optional: nothing else should ever be a `ValView` by
|
|
4
|
+
* accident, and reading a property off one has to be an error.
|
|
5
|
+
*/
|
|
6
|
+
declare const ViewTarget: unique symbol;
|
|
7
|
+
/**
|
|
8
|
+
* A pointer to another module, as consuming code sees it.
|
|
9
|
+
*
|
|
10
|
+
* It has no properties on purpose. A view exists for the Val editor — it puts a
|
|
11
|
+
* way into another module on this module's screen — and there is nothing in it
|
|
12
|
+
* to render. Read the module it names directly.
|
|
13
|
+
*
|
|
14
|
+
* `T` is the target's source type. It is what lets `useVal(page.header)` be
|
|
15
|
+
* typed as the header's content — the overload that resolves a handle
|
|
16
|
+
* constrains `T` to `Source` there rather than here, because `ValViewSource` is a
|
|
17
|
+
* member of the `Source` union and a constraint here is a circular reference.
|
|
18
|
+
*/
|
|
19
|
+
export type ValView<T> = {
|
|
20
|
+
readonly [ViewTarget]: T;
|
|
21
|
+
};
|
|
22
|
+
export {};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { ExternalRecordSrc } from "./external.js";
|
|
2
|
+
import { ValViewSource } from "./view.js";
|
|
2
3
|
import { JsonSource } from "./json.js";
|
|
3
4
|
import { MediaSource } from "./media.js";
|
|
4
5
|
import { RichTextOptions, RichTextSource } from "./richtext.js";
|
|
5
6
|
import { SettingsSource } from "./settings.js";
|
|
6
|
-
export type Source = SourcePrimitive | SourceObject | SourceArray | MediaSource | JsonSource | ExternalRecordSrc | SettingsSource | RichTextSource<RichTextOptions>;
|
|
7
|
+
export type Source = SourcePrimitive | SourceObject | SourceArray | MediaSource | JsonSource | ExternalRecordSrc | ValViewSource | SettingsSource | RichTextSource<RichTextOptions>;
|
|
7
8
|
export type SourceObject = {
|
|
8
9
|
[key in string]: Source;
|
|
9
10
|
} & {
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type-level-only slot carrying the SOURCE TYPE of the module a view names.
|
|
3
|
+
*
|
|
4
|
+
* Optional, and that is what makes the marker writable: `{ view: "/foo.val.ts" }`
|
|
5
|
+
* is a plain literal, and an optional symbol-keyed property costs the author
|
|
6
|
+
* nothing to satisfy. It exists so the read side can say WHAT is behind the
|
|
7
|
+
* pointer — `ValView<FooSrc>` rather than `ValView<unknown>` — without the
|
|
8
|
+
* author having to write a type they do not have.
|
|
9
|
+
*/
|
|
10
|
+
declare const ViewTargetSource: unique symbol;
|
|
11
|
+
/**
|
|
12
|
+
* The source of a view field: a pointer to another module, and nothing else.
|
|
13
|
+
*
|
|
14
|
+
* Written as a plain object, like media and for the same reason — the same
|
|
15
|
+
* value has to work in a `.val.ts` and in a `*.val.json` entry, and a literal
|
|
16
|
+
* survives the static extraction that a constructor call does not. See
|
|
17
|
+
* `architecture/media.md`.
|
|
18
|
+
*
|
|
19
|
+
* `Id` is the target's module file path as a LITERAL type, inferred from the
|
|
20
|
+
* module handed to `s.view(...)`. So the path autocompletes at the call site,
|
|
21
|
+
* and a source naming a different module than its schema does is a type error.
|
|
22
|
+
*
|
|
23
|
+
* Nothing decides "this is a view" by looking at the value; the SCHEMA does
|
|
24
|
+
* (`type === "view"`). {@link isValViewSource} exists for the few places that have
|
|
25
|
+
* a value and no schema, and is not the normal way to ask.
|
|
26
|
+
*/
|
|
27
|
+
export type ValViewSource<Id extends string = string, T = unknown> = {
|
|
28
|
+
readonly view: Id;
|
|
29
|
+
} & {
|
|
30
|
+
readonly [ViewTargetSource]?: T;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Is this value shaped like a view marker?
|
|
34
|
+
*
|
|
35
|
+
* A shape test, not an identity: `view` is a reserved object key
|
|
36
|
+
* (`ObjectSchemaProps`), so no ordinary object source can take this shape — but
|
|
37
|
+
* hand-written JSON can, which is why validation checks the value against the
|
|
38
|
+
* schema rather than trusting it.
|
|
39
|
+
*/
|
|
40
|
+
export declare function isValViewSource(value: unknown): value is ValViewSource;
|
|
41
|
+
export {};
|