@valbuild/core 0.120.0 → 0.124.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/CHANGELOG.md +123 -0
  2. package/dist/declarations/src/index.d.ts +12 -1
  3. package/dist/declarations/src/initSchema.d.ts +27 -0
  4. package/dist/declarations/src/initVal.d.ts +13 -8
  5. package/dist/declarations/src/module.d.ts +18 -1
  6. package/dist/declarations/src/schema/array.d.ts +2 -2
  7. package/dist/declarations/src/schema/boolean.d.ts +2 -2
  8. package/dist/declarations/src/schema/code.d.ts +2 -2
  9. package/dist/declarations/src/schema/color.d.ts +2 -2
  10. package/dist/declarations/src/schema/date.d.ts +2 -2
  11. package/dist/declarations/src/schema/datetime.d.ts +2 -2
  12. package/dist/declarations/src/schema/file.d.ts +2 -2
  13. package/dist/declarations/src/schema/files.d.ts +2 -5
  14. package/dist/declarations/src/schema/hasRemoteFileSchema.d.ts +21 -3
  15. package/dist/declarations/src/schema/image.d.ts +2 -2
  16. package/dist/declarations/src/schema/images.d.ts +1 -6
  17. package/dist/declarations/src/schema/index.d.ts +12 -3
  18. package/dist/declarations/src/schema/keyOf.d.ts +2 -2
  19. package/dist/declarations/src/schema/literal.d.ts +2 -2
  20. package/dist/declarations/src/schema/number.d.ts +2 -2
  21. package/dist/declarations/src/schema/object.d.ts +2 -2
  22. package/dist/declarations/src/schema/record.d.ts +80 -4
  23. package/dist/declarations/src/schema/richtext.d.ts +2 -2
  24. package/dist/declarations/src/schema/route.d.ts +2 -2
  25. package/dist/declarations/src/schema/settings.d.ts +102 -0
  26. package/dist/declarations/src/schema/string.d.ts +2 -2
  27. package/dist/declarations/src/schema/union.d.ts +2 -2
  28. package/dist/declarations/src/schema/validation/ValidationFix.d.ts +1 -1
  29. package/dist/declarations/src/selector/index.d.ts +4 -2
  30. package/dist/declarations/src/settingsModule.d.ts +60 -0
  31. package/dist/declarations/src/source/external.d.ts +91 -0
  32. package/dist/declarations/src/source/externalRef.d.ts +46 -0
  33. package/dist/declarations/src/source/index.d.ts +4 -2
  34. package/dist/declarations/src/source/settings.d.ts +94 -0
  35. package/dist/{index-861944d8.cjs.prod.js → index-22a40d42.cjs.prod.js} +1324 -446
  36. package/dist/{index-2f65e194.esm.js → index-9c14df37.esm.js} +1318 -447
  37. package/dist/{index-01f673d2.cjs.dev.js → index-fcd087bd.cjs.dev.js} +1324 -446
  38. package/dist/valbuild-core.cjs.dev.js +8 -1
  39. package/dist/valbuild-core.cjs.prod.js +8 -1
  40. package/dist/valbuild-core.esm.js +1 -1
  41. package/package.json +1 -1
  42. package/patch/dist/valbuild-core-patch.cjs.dev.js +1 -1
  43. package/patch/dist/valbuild-core-patch.cjs.prod.js +1 -1
  44. package/patch/dist/valbuild-core-patch.esm.js +2 -2
@@ -4,6 +4,7 @@ import { FieldRender } from "../render.js";
4
4
  import { ValRouter } from "../router.js";
5
5
  import { SelectorSource } from "../selector/index.js";
6
6
  import { JsonOf, JsonSource } from "../source/json.js";
7
+ import { ExternalRecordSrc } from "../source/external.js";
7
8
  import { ModuleFilePath, SourcePath } from "../val/index.js";
8
9
  import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
9
10
  import type { ImageEncodeOption } from "./image.js";
@@ -38,6 +39,12 @@ export type SerializedRecordSchema = {
38
39
  encode?: ImageEncodeOption;
39
40
  alt?: SerializedSchema;
40
41
  jsonValues?: boolean;
42
+ /**
43
+ * The label from `.external(label)`. Its presence is what marks the record as
44
+ * externally stored; the value names the binding a reader — or a model — can
45
+ * grep for. See `source/external.ts`.
46
+ */
47
+ external?: string;
41
48
  readonly?: boolean;
42
49
  hidden?: boolean;
43
50
  description?: string;
@@ -56,7 +63,13 @@ export type SerializedRecordSchema = {
56
63
  * authoring.
57
64
  */
58
65
  export type JsonValuesRecordSrc<T extends Schema<SelectorSource>, K extends Schema<string>> = Record<SelectorOfSchema<K>, JsonSource<JsonOf<SelectorOfSchema<T>>> | SelectorOfSchema<T>>;
59
- export declare class RecordSchema<T extends Schema<SelectorSource>, K extends Schema<string>, Src extends Record<SelectorOfSchema<K>, SelectorOfSchema<T>> | JsonValuesRecordSrc<T, K> | null> extends Schema<Src> {
66
+ export declare class RecordSchema<T extends Schema<SelectorSource>, K extends Schema<string>, Src extends Record<SelectorOfSchema<K>, SelectorOfSchema<T>> | JsonValuesRecordSrc<T, K> | ExternalRecordSrc | null,
67
+ /**
68
+ * Whether `.readonly()` was called. It rides in the type — not only in
69
+ * `isReadonly` — because `.external()` freezes it into the source marker, and
70
+ * the adapter's write methods are required or forbidden on the strength of it.
71
+ */
72
+ RO extends boolean = false> extends Schema<Src> {
60
73
  private readonly item;
61
74
  private readonly opt;
62
75
  private readonly customValidateFunctions;
@@ -70,9 +83,31 @@ export declare class RecordSchema<T extends Schema<SelectorSource>, K extends Sc
70
83
  private readonly isJsonValues;
71
84
  private readonly previewInput;
72
85
  private readonly renderInput;
86
+ /**
87
+ * Set by `.external(label)`; the entries live behind that adapter.
88
+ *
89
+ * LAST on purpose: every other `new RecordSchema(...,
90
+ this.externalLabel,
91
+ )` in the codebase
92
+ * passes its arguments positionally, so a new parameter anywhere else
93
+ * silently shifts `previewInput` and `renderInput` at each of those call
94
+ * sites.
95
+ */
96
+ private readonly externalLabel;
73
97
  constructor(item: T, opt?: boolean, customValidateFunctions?: CustomValidateFunction<Src>[], currentRouter?: ValRouter | null, keySchema?: Schema<string> | null, mediaOptions?: MediaOptions | undefined, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined,
74
98
  /** When true, entry values are lazily loaded {@link JsonSource} thunks. */
75
- isJsonValues?: boolean, previewInput?: ItemPreviewInput<Src> | null, renderInput?: FieldRender | null);
99
+ isJsonValues?: boolean, previewInput?: ItemPreviewInput<Src> | null, renderInput?: FieldRender | null,
100
+ /**
101
+ * Set by `.external(label)`; the entries live behind that adapter.
102
+ *
103
+ * LAST on purpose: every other `new RecordSchema(...,
104
+ this.externalLabel,
105
+ )` in the codebase
106
+ * passes its arguments positionally, so a new parameter anywhere else
107
+ * silently shifts `previewInput` and `renderInput` at each of those call
108
+ * sites.
109
+ */
110
+ externalLabel?: string | null);
76
111
  describe(description: string | null): RecordSchema<T, K, Src>;
77
112
  validate(validationFunction: (src: Src) => false | string): RecordSchema<T, K, Src>;
78
113
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
@@ -84,8 +119,18 @@ export declare class RecordSchema<T extends Schema<SelectorSource>, K extends Sc
84
119
  private validateMediaMimeType;
85
120
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
86
121
  nullable(): RecordSchema<T, K, Src | null>;
87
- readonly(): RecordSchema<T, K, Src>;
88
- hidden(): RecordSchema<T, K, Src>;
122
+ /**
123
+ * A record you can look at but not change.
124
+ *
125
+ * The flag rides in the TYPE as well as in the instance, so `.external()` can
126
+ * freeze it into the source marker and the adapter's write methods become
127
+ * required or forbidden on the strength of it. That is also why `.readonly()`
128
+ * must come BEFORE `.external()`: afterwards the value is a `Schema`, and
129
+ * there is no `.readonly()` left to call — which is the error the compiler
130
+ * gives, and a clearer one than a runtime throw.
131
+ */
132
+ readonly<B extends boolean = true>(isReadonly?: B): RecordSchema<T, K, Src, B>;
133
+ hidden(isHidden?: boolean): RecordSchema<T, K, Src>;
89
134
  router(router: ValRouter): RecordSchema<T, K, Src>;
90
135
  remote(): RecordSchema<T, K, Src>;
91
136
  /**
@@ -103,6 +148,37 @@ export declare class RecordSchema<T extends Schema<SelectorSource>, K extends Sc
103
148
  * substitution and content validation are all root-only.
104
149
  */
105
150
  jsonValues(): RecordSchema<T, K, JsonValuesRecordSrc<T, K>>;
151
+ /**
152
+ * Store this record's entries behind an adapter — a database, an HTTP API, a
153
+ * bucket — instead of in the module.
154
+ *
155
+ * Available on every record-derived schema, which is `s.record()`,
156
+ * `s.router()`, `s.images()` and `s.files()` alike: a router is a
157
+ * `RecordSchema` with a `ValRouter`, a gallery one with media options, so all
158
+ * four get external storage from this one method.
159
+ *
160
+ * `label` does not address the binding — the registry is keyed by module, and
161
+ * so is the wire protocol. It exists so a reader of the schema can see what
162
+ * backs it, and it is checked against the binding that claims it, so the two
163
+ * cannot drift.
164
+ *
165
+ * Only supported on a module's ROOT record, exactly as `.jsonValues()` is: a
166
+ * nested external record would leave a hole in the middle of a module's
167
+ * source, and the Studio, patching and validation all assume a module's
168
+ * source is complete apart from its entries.
169
+ */
170
+ external<L extends string>(label: L): RecordSchema<T, K, ExternalRecordSrc<SelectorOfSchema<T>, L, RO>, RO>;
171
+ /**
172
+ * An external record's source is either the `c.external()` marker or entries
173
+ * still written inline in the `.val.ts`.
174
+ *
175
+ * Inline entries are NOT a type error (see `ExternalRecordWritableSrc`), so
176
+ * this is where they surface: as an `external:upload` fix, per key, the way
177
+ * `.jsonValues()` reports an un-extracted entry. Reporting per key rather than
178
+ * once for the record is what lets the fix be applied to one entry, and what
179
+ * puts the error at a path the editor can navigate to.
180
+ */
181
+ private validateExternal;
106
182
  private getRouterValidations;
107
183
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
108
184
  protected executeSerialize(): SerializedRecordSchema;
@@ -40,8 +40,8 @@ export declare class RichTextSchema<O extends RichTextOptions, Src extends RichT
40
40
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
41
41
  private recursiveAssert;
42
42
  nullable(): RichTextSchema<O, Src | null>;
43
- readonly(): RichTextSchema<O, Src>;
44
- hidden(): RichTextSchema<O, Src>;
43
+ readonly(isReadonly?: boolean): RichTextSchema<O, Src>;
44
+ hidden(isHidden?: boolean): RichTextSchema<O, Src>;
45
45
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
46
46
  /**
47
47
  * How this field is laid out in the editor when it is the item of an array
@@ -71,8 +71,8 @@ export declare class RouteSchema<Src extends string | null> extends Schema<Src>
71
71
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
72
72
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
73
73
  nullable(): RouteSchema<Src | null>;
74
- readonly(): RouteSchema<Src>;
75
- hidden(): RouteSchema<Src>;
74
+ readonly(isReadonly?: boolean): RouteSchema<Src>;
75
+ hidden(isHidden?: boolean): RouteSchema<Src>;
76
76
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
77
77
  /**
78
78
  * How this field is laid out in the editor when it is the item of an array
@@ -0,0 +1,102 @@
1
+ import { Schema, SchemaAssertResult, SerializedSchema } from "./index.js";
2
+ import { PreviewScope, ReifiedPreview } from "../preview.js";
3
+ import { FieldRender } from "../render.js";
4
+ import { SelectorSource } from "../selector/index.js";
5
+ import { SettingsSource } from "../source/settings.js";
6
+ import { ModuleFilePath, SourcePath } from "../val/index.js";
7
+ import { ValidationError, ValidationErrors } from "./validation/ValidationError.js";
8
+ export type SerializedSettingsSchema = {
9
+ type: "settings";
10
+ /**
11
+ * The sections this settings schema knows about, keyed by name.
12
+ *
13
+ * Carried in full rather than reconstructed from the Val version, so a Studio
14
+ * that predates a section still deserializes the schema (it renders what it
15
+ * recognises and leaves the rest alone) instead of failing on an unknown
16
+ * shape.
17
+ */
18
+ items: Record<string, SerializedSchema>;
19
+ opt: boolean;
20
+ /**
21
+ * Present in the shape, never set by `s.settings()`.
22
+ *
23
+ * `render` and `preview` say how a value is drawn where it is an ITEM of an
24
+ * array or a record, and `customValidate` marks a user's `validate` closure. A
25
+ * settings module is not an item, and `s.settings()` takes no arguments to
26
+ * declare any of the three with — so nothing writes them.
27
+ *
28
+ * They are declared anyway, and with their real types rather than `never`, for
29
+ * two reasons: reading `schema.render` off a `SerializedSchema` has to stay
30
+ * legal (`isInlineRender` does exactly that), and a `?: never` member is typed
31
+ * `undefined`, which makes the whole `SerializedSchema` union stop being
32
+ * assignable to `Json` — and serialized schemas travel to the Studio and to
33
+ * the MCP tools as JSON.
34
+ */
35
+ render?: FieldRender;
36
+ preview?: true;
37
+ customValidate?: boolean;
38
+ readonly?: boolean;
39
+ hidden?: boolean;
40
+ description?: string;
41
+ };
42
+ /**
43
+ * An object whose keys are all optional: absent means unset.
44
+ *
45
+ * Both the settings module itself and each of its sections are this. See
46
+ * {@link SettingsSource} for why settings cannot be an object schema, and
47
+ * {@link settings} for what a settings module holds.
48
+ *
49
+ * There is no `describe`, `validate`, `render` or `preview` on purpose: the
50
+ * shape is Val's, not the schema author's — `s.settings()` takes no arguments —
51
+ * and the Studio renders each section with a UI built for it.
52
+ */
53
+ export declare class SettingsSchema<Src extends {
54
+ [key: string]: SelectorSource;
55
+ } | null> extends Schema<Src> {
56
+ private readonly items;
57
+ private readonly opt;
58
+ private readonly isReadonly;
59
+ private readonly isHidden;
60
+ private readonly description?;
61
+ constructor(items: Record<string, Schema<SelectorSource>>, opt?: boolean, isReadonly?: boolean, isHidden?: boolean, description?: string | undefined);
62
+ protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
63
+ protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
64
+ nullable(): SettingsSchema<Src | null>;
65
+ readonly(): SettingsSchema<Src>;
66
+ hidden(): SettingsSchema<Src>;
67
+ protected executeCustomValidateAt(): ValidationError[];
68
+ protected executeSerialize(): SerializedSchema;
69
+ protected executePreview(sourcePath: SourcePath | ModuleFilePath, src: Src, scope?: PreviewScope): ReifiedPreview;
70
+ }
71
+ /**
72
+ * Define the project's settings.
73
+ *
74
+ * One settings module per project, at the root of the content tree — a module
75
+ * file path with no directory segment, `/settings.val.ts` by convention:
76
+ *
77
+ * ```typescript
78
+ * export default c.define("/settings.val.ts", s.settings(), {});
79
+ * ```
80
+ *
81
+ * Every section is optional, so `{}` is a complete settings module and stays
82
+ * one as sections are added. Fill in what the project needs:
83
+ *
84
+ * ```typescript
85
+ * export default c.define("/settings.val.ts", s.settings(), {
86
+ * assistant: {
87
+ * enabled: true,
88
+ * context: "Val is a CMS for developers. British English, and 'Val' is never 'VAL'.",
89
+ * tone: "Plain and direct. No exclamation marks, sentence case in headings.",
90
+ * },
91
+ * });
92
+ * ```
93
+ *
94
+ * The Studio edits it under the cog at the foot of the left rail, and the
95
+ * assistant is told `assistant.context` and `assistant.tone` on every message.
96
+ *
97
+ * `s.settings()` takes no arguments: the shape is Val's, which is what lets the
98
+ * Studio render a UI built for each field rather than a generic form. A
99
+ * project's own global content — a site name, social links — belongs in a
100
+ * module of its own.
101
+ */
102
+ export declare function settings(): SettingsSchema<SettingsSource>;
@@ -82,8 +82,8 @@ export declare class StringSchema<Src extends string | null> extends Schema<Src>
82
82
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
83
83
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
84
84
  nullable(): StringSchema<Src | null>;
85
- readonly(): StringSchema<Src>;
86
- hidden(): StringSchema<Src>;
85
+ readonly(isReadonly?: boolean): StringSchema<Src>;
86
+ hidden(isHidden?: boolean): StringSchema<Src>;
87
87
  raw(): StringSchema<Src extends null ? RawString | null : RawString>;
88
88
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
89
89
  protected executeSerialize(): SerializedSchema;
@@ -56,8 +56,8 @@ export declare class UnionSchema<Key extends string | Schema<string>, T extends
56
56
  protected executeValidate(path: SourcePath, src: Src): ValidationErrors;
57
57
  protected executeAssert(path: SourcePath, src: unknown): SchemaAssertResult<Src>;
58
58
  nullable(): UnionSchema<Key, T, Src | null>;
59
- readonly(): UnionSchema<Key, T, Src>;
60
- hidden(): UnionSchema<Key, T, Src>;
59
+ readonly(isReadonly?: boolean): UnionSchema<Key, T, Src>;
60
+ hidden(isHidden?: boolean): UnionSchema<Key, T, Src>;
61
61
  protected executeCustomValidateAt(path: SourcePath, src: Src): ValidationError[];
62
62
  /**
63
63
  * The variant schema a value takes, for a TAGGED union (string `key`). `null`
@@ -1,2 +1,2 @@
1
- export declare const ValidationFix: readonly ["image:add-metadata", "image:check-metadata", "image:upload-remote", "image:download-remote", "image:check-remote", "images:check-remote", "images:upload-remote", "file:add-metadata", "file:check-metadata", "file:upload-remote", "file:download-remote", "file:check-remote", "files:check-remote", "files:upload-remote", "keyof:check-keys", "router:check-route", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "jsonValues:extract-entry"];
1
+ export declare const ValidationFix: readonly ["image:add-metadata", "image:check-metadata", "image:upload-remote", "image:download-remote", "image:check-remote", "images:check-remote", "images:upload-remote", "file:add-metadata", "file:check-metadata", "file:upload-remote", "file:download-remote", "file:check-remote", "files:check-remote", "files:upload-remote", "keyof:check-keys", "router:check-route", "images:check-unique-folder", "files:check-unique-folder", "images:check-all-files", "files:check-all-files", "jsonValues:extract-entry", "external:upload"];
2
2
  export type ValidationFix = (typeof ValidationFix)[number];
@@ -12,10 +12,12 @@ import { MediaSource } from "../source/media.js";
12
12
  import { AllRichTextOptions, RichTextSource } from "../source/richtext.js";
13
13
  import { RichTextSelector } from "./richtext.js";
14
14
  import { JsonSource } from "../source/json.js";
15
- export type Selector<T extends Source> = Source extends T ? GenericSelector<T> : T extends MediaSource ? GenericSelector<T> : T extends JsonSource ? GenericSelector<JsonSource> : T extends RichTextSource<infer O> ? RichTextSelector<O> : T extends SourceObject ? ObjectSelector<T> : T extends SourceArray ? ArraySelector<T> : T extends string ? StringSelector<T> : T extends number ? NumberSelector<T> : T extends boolean ? BooleanSelector<T> : T extends null ? PrimitiveSelector<null> : never;
15
+ import { ExternalRecordSrc } from "../source/external.js";
16
+ 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;
16
18
  export type SelectorSource = SourcePrimitive | undefined | readonly SelectorSource[] | {
17
19
  [key: string]: SelectorSource;
18
- } | MediaSource | JsonSource | RichTextSource<AllRichTextOptions> | GenericSelector<Source>;
20
+ } | MediaSource | JsonSource | ExternalRecordSrc | SettingsSource | RichTextSource<AllRichTextOptions> | GenericSelector<Source>;
19
21
  /**
20
22
  * @internal
21
23
  */
@@ -0,0 +1,60 @@
1
+ import { SerializedSchema } from "./schema/index.js";
2
+ import { ModuleFilePath } from "./val/index.js";
3
+ /**
4
+ * Where a settings module goes unless a project has a reason not to.
5
+ *
6
+ * A convention, not a rule: any module file path at the root of the content
7
+ * tree may hold `s.settings()` — see {@link resolveSettingsModule}.
8
+ */
9
+ export declare const SETTINGS_MODULE_CONVENTION: ModuleFilePath;
10
+ /**
11
+ * Whether a module file path is at the root of the content tree.
12
+ *
13
+ * `/settings.val.ts` is; `/content/settings.val.ts` is not. The settings module
14
+ * is the project's, not a folder's, and a project-wide setting found three
15
+ * directories down reads as one section's — so the position is part of what it
16
+ * means.
17
+ */
18
+ export declare function isRootModuleFilePath(moduleFilePath: string): boolean;
19
+ export type ResolvedSettingsModule = {
20
+ /**
21
+ * The project's settings module, if it has exactly one valid one.
22
+ *
23
+ * `null` both when there is no settings module and when what there is cannot
24
+ * be used (nested, or ambiguous). A caller that reads settings therefore does
25
+ * not have to consider the broken cases: it gets nothing, and the errors are
26
+ * reported by whoever is in a position to report them.
27
+ */
28
+ moduleFilePath: ModuleFilePath | null;
29
+ /**
30
+ * What is wrong with how settings are declared, ready to print.
31
+ *
32
+ * Empty in the ordinary cases — one settings module, or none. Reported as
33
+ * MODULE errors, each attributed to the file it is about: extraction appends
34
+ * them, which is what puts them in front of the reader in all three places at
35
+ * once — the dev server refuses to serve sources with a module error,
36
+ * `val validate` reports them against the file, and the Studio shows them.
37
+ *
38
+ * A duplicate produces one error per offending module, each naming all of
39
+ * them, so the message is there whichever of the files you happen to open.
40
+ */
41
+ errors: SettingsModuleError[];
42
+ };
43
+ /**
44
+ * Structurally an `ExtractedModuleError`, declared here rather than imported to
45
+ * keep `extractValModules` -> `settingsModule` a one-way dependency.
46
+ */
47
+ export type SettingsModuleError = {
48
+ message: string;
49
+ path: ModuleFilePath;
50
+ };
51
+ /**
52
+ * Find the project's settings module among all its schemas.
53
+ *
54
+ * One per project, at the root: a settings module in a subdirectory is an
55
+ * error, and so is a second one. Both are reported rather than resolved by
56
+ * picking a winner — a project with two settings modules has a question to
57
+ * answer, and answering it by sort order means the answer changes when a file
58
+ * is renamed.
59
+ */
60
+ export declare function resolveSettingsModule(schemas: Record<ModuleFilePath, SerializedSchema> | Record<ModuleFilePath, SerializedSchema | undefined>): ResolvedSettingsModule;
@@ -0,0 +1,91 @@
1
+ import { Source, VAL_EXTENSION } from "./index.js";
2
+ /**
3
+ * The string used as the `_type` discriminator of an {@link ExternalRecordSrc}.
4
+ */
5
+ export declare const EXTERNAL_VAL_EXTENSION_TAG: "external";
6
+ /**
7
+ * Type-level-only slots. Declared here rather than reusing `PhantomType`
8
+ * (`source/index.ts`) because three separate facts have to ride along, and that
9
+ * helper carries one.
10
+ *
11
+ * None of them exist at runtime: `c.external()` produces `{ _type: "external" }`
12
+ * and nothing else.
13
+ */
14
+ declare const ExternalItem: unique symbol;
15
+ declare const ExternalLabel: unique symbol;
16
+ declare const ExternalReadonly: unique symbol;
17
+ /**
18
+ * The source of a record whose entries live behind an adapter — a database, an
19
+ * HTTP API, a bucket — instead of in the module.
20
+ *
21
+ * This is what `c.external()` returns, and it is the counterpart of the
22
+ * `c.json(() => import(...))` marker a `.jsonValues()` entry carries: a value
23
+ * that says "the content is not here" without saying anything about what is.
24
+ * The schema says *which* adapter (`.external("posts")`); this says *that* the
25
+ * entries are elsewhere, which is what every source-walking consumer needs to
26
+ * know without holding a schema.
27
+ *
28
+ * Three phantom parameters, each earning its place:
29
+ *
30
+ * - `T` — the item type. Without it `c.define` would return a module whose type
31
+ * is "a marker", the item type would be gone, and an adapter could not be
32
+ * typed from the schema at all.
33
+ * - `L` — the label from `.external(label)`, so a binding can be checked against
34
+ * the schema that declared it.
35
+ * - `RO` — whether the schema said `.readonly()`, which decides whether the
36
+ * adapter must implement writes or is forbidden from doing so.
37
+ *
38
+ * The defaults are the WIDEST instantiation on purpose: the bare name appears in
39
+ * the `Source` and `SelectorSource` unions, and every narrower instantiation has
40
+ * to be assignable to it. The `external()` factory below defaults the other way,
41
+ * exactly as `JsonSource<T = unknown>` and `json<T = Json>()` do.
42
+ */
43
+ export type ExternalRecordSrc<T = unknown, L extends string = string, RO extends boolean = boolean> = {
44
+ readonly [VAL_EXTENSION]: typeof EXTERNAL_VAL_EXTENSION_TAG;
45
+ } & {
46
+ readonly [ExternalItem]: T;
47
+ readonly [ExternalLabel]: L;
48
+ readonly [ExternalReadonly]: RO;
49
+ };
50
+ /**
51
+ * The marker written as a module's source when its record is `.external()`.
52
+ *
53
+ * Takes no arguments: everything it carries is inferred from the schema it is
54
+ * checked against in `c.define`. The assertion mirrors `json()` — the phantom
55
+ * slots are symbol-keyed and have no runtime representation, so there is nothing
56
+ * to construct for them.
57
+ */
58
+ export declare function external<T = never, L extends string = string, RO extends boolean = false>(): ExternalRecordSrc<T, L, RO>;
59
+ export declare function isExternal(obj: unknown): obj is ExternalRecordSrc;
60
+ /**
61
+ * The item type an external record holds, read back off a module.
62
+ *
63
+ * `GenericSelector` is not imported here — that would be a cycle through
64
+ * `../selector` — so this is written against the phantom slot directly and the
65
+ * selector-shaped overloads live where the selector types do.
66
+ */
67
+ export type ExternalItemOf<S> = S extends ExternalRecordSrc<infer I, string, boolean> ? I : never;
68
+ /** The label an external record was declared with. */
69
+ export type ExternalLabelOf<S> = S extends ExternalRecordSrc<unknown, infer L, boolean> ? L : never;
70
+ /** Whether the schema behind an external record said `.readonly()`. */
71
+ export type ExternalReadonlyOf<S> = S extends ExternalRecordSrc<unknown, string, infer RO> ? RO : never;
72
+ /**
73
+ * The source of an external record as it may be WRITTEN.
74
+ *
75
+ * Either the marker, or entries inline in the `.val.ts`. Inline entries are
76
+ * accepted by the type on purpose, for the same reason `.jsonValues()` accepts
77
+ * them (see `JsonValuesRecordSrc`): pasting content straight into the module is
78
+ * the natural first thing to write, and a type error there is a dead end — the
79
+ * author cannot see what to write instead. Validation reports the inline entries
80
+ * as an `external:upload` fix instead, so the mistake is repaired rather than
81
+ * blocking authoring, and they are readable at runtime meanwhile.
82
+ */
83
+ export type ExternalRecordWritableSrc<Item, Key extends string = string, L extends string = string, RO extends boolean = boolean> = ExternalRecordSrc<Item, L, RO> | Record<Key, Item>;
84
+ /**
85
+ * Is this module source an external record that still has entries written
86
+ * inline? Used by validation to raise `external:upload`.
87
+ */
88
+ export declare function hasInlineExternalEntries(source: Source): source is {
89
+ [key: string]: Source;
90
+ };
91
+ export {};
@@ -0,0 +1,46 @@
1
+ /**
2
+ * The reference stored for a file whose bytes live behind an external adapter.
3
+ *
4
+ * Deliberately the same shape of idea as a remote ref (`remote/splitRemoteRef`):
5
+ * a URL-ish string that **ends with the file's virtual path**, so the file's
6
+ * logical identity survives wherever its bytes happen to be.
7
+ *
8
+ * /api/val/external/documents/f/{fileHash}/p/public/val/documents/report_a1b2c.pdf
9
+ * \__________________________/ \________/ \_______________________________/
10
+ * route + label content the path a LOCAL file
11
+ * address would have had
12
+ *
13
+ * Three things follow from that shape, and each is load-bearing:
14
+ *
15
+ * - **`media.ts` needs no change.** `isRemote` is `!path.startsWith("/public")`,
16
+ * and this does not, so `Internal.mediaUrl` returns the ref as-is and Val's
17
+ * route serves it. The media rule that has been got wrong repeatedly is left
18
+ * exactly alone.
19
+ * - **Moving between storage modes is mechanical.** External → local reads the
20
+ * `public/…` suffix back out, fetches the bytes and writes them there; local →
21
+ * external uploads the file already at that path. Nothing is renamed.
22
+ * - **The filename is Val's, not the adapter's**, produced by the same
23
+ * `createFilename` a local or remote file uses — so the same bytes get the
24
+ * same name under all three storage modes.
25
+ */
26
+ export type ExternalFileRefParts = {
27
+ /** The `.external(label)` of the record the file belongs to. */
28
+ label: string;
29
+ /** SHA-256 of the bytes. Content-addressed, so a retried publish re-uses. */
30
+ fileHash: string;
31
+ /** The virtual path, WITHOUT a leading slash: `public/val/...`. */
32
+ filePath: `public/${string}`;
33
+ };
34
+ export declare function createExternalFileRef({ label, fileHash, filePath, }: {
35
+ label: string;
36
+ fileHash: string;
37
+ /** Accepts the path as it is written in a module: `/public/val/...`. */
38
+ filePath: string;
39
+ }): string;
40
+ export declare function splitExternalFileRef(ref: string): ({
41
+ status: "success";
42
+ } & ExternalFileRefParts) | {
43
+ status: "error";
44
+ error: string;
45
+ };
46
+ export declare function isExternalFileRef(ref: string): boolean;
@@ -1,7 +1,9 @@
1
+ import { ExternalRecordSrc } from "./external.js";
1
2
  import { JsonSource } from "./json.js";
2
3
  import { MediaSource } from "./media.js";
3
4
  import { RichTextOptions, RichTextSource } from "./richtext.js";
4
- export type Source = SourcePrimitive | SourceObject | SourceArray | MediaSource | JsonSource | RichTextSource<RichTextOptions>;
5
+ import { SettingsSource } from "./settings.js";
6
+ export type Source = SourcePrimitive | SourceObject | SourceArray | MediaSource | JsonSource | ExternalRecordSrc | SettingsSource | RichTextSource<RichTextOptions>;
5
7
  export type SourceObject = {
6
8
  [key in string]: Source;
7
9
  } & {
@@ -21,7 +23,7 @@ export type SourceObject = {
21
23
  export type SourceArray = readonly Source[];
22
24
  export type SourcePrimitive = string | number | boolean | null;
23
25
  export declare const VAL_EXTENSION: "_type";
24
- export declare function getValExtension(source: Source): false | "" | 0 | "json" | null | undefined;
26
+ export declare function getValExtension(source: Source): false | "" | 0 | "json" | "external" | null | undefined;
25
27
  /**
26
28
  * A phantom type parameter is one that doesn't show up at runtime, but is checked statically (and only) at compile time.
27
29
  *
@@ -0,0 +1,94 @@
1
+ /**
2
+ * The source of the project's settings module — `s.settings()`.
3
+ *
4
+ * **Every key is optional, at every level, and that is the whole point.** A
5
+ * settings module is written as
6
+ *
7
+ * ```typescript
8
+ * export default c.define("/settings.val.ts", s.settings(), {});
9
+ * ```
10
+ *
11
+ * and `{}` has to keep validating as sections are added to it. A project that
12
+ * never touched the assistant's settings should not have to edit its settings
13
+ * file the day `locales` or `permissions` land.
14
+ *
15
+ * That is why settings is not sugar over `s.object()`: an object schema errors
16
+ * with `Expected key 'assistant' not found in object` for any absent key (see
17
+ * `ObjectSchema.executeAssert`), and `.nullable()` permits `null`, not absence.
18
+ * `SettingsSchema` gives absent keys the meaning "unset" instead.
19
+ *
20
+ * `null` means unset too, so the editor can clear a field it has already
21
+ * written without removing the key.
22
+ */
23
+ export type SettingsSource = {
24
+ assistant?: AssistantSettingsSource;
25
+ };
26
+ /**
27
+ * The assistant: whether editors have one, and what it is told about the
28
+ * project on every message.
29
+ *
30
+ * `context` and `tone` are prose the model reads, not instructions Val
31
+ * interprets, and both are capped (see {@link ASSISTANT_SETTINGS_MAX_LENGTH}):
32
+ * they are prepended to every request the chat makes, so an unbounded field
33
+ * here is an unbounded cost on every turn.
34
+ */
35
+ export type AssistantSettingsSource = {
36
+ /**
37
+ * Whether editors have an assistant in this project.
38
+ *
39
+ * Three states, not two — see {@link assistantAvailability}, which is the one
40
+ * place that reads them. Unset is NOT "on": it is "nobody has decided", and
41
+ * the Studio treats that as an offer rather than as an answer.
42
+ */
43
+ enabled?: boolean | null;
44
+ /**
45
+ * Background the model would otherwise have to guess: what this site is, who
46
+ * runs it, what the product does, names and spellings that matter.
47
+ */
48
+ context?: string | null;
49
+ /**
50
+ * How the model should write when it writes content — formal or playful,
51
+ * British or American, sentence case in headings, no exclamation marks.
52
+ *
53
+ * Named `tone` rather than `toneOfVoice` so it reads as one hint among
54
+ * several inside `assistant`, leaving room for siblings (`audience`,
55
+ * `glossary`) rather than a field that looks like it should absorb them.
56
+ */
57
+ tone?: string | null;
58
+ };
59
+ /**
60
+ * The cap on each of the assistant's prose fields, in characters.
61
+ *
62
+ * Roughly a thousand tokens each. Generous enough for a paragraph of
63
+ * background and a house style, small enough that the two of them together do
64
+ * not dominate the system prompt they are appended to.
65
+ */
66
+ export declare const ASSISTANT_SETTINGS_MAX_LENGTH = 4000;
67
+ /**
68
+ * Whether a project has an assistant — and the answer is not a boolean.
69
+ *
70
+ * - `"on"`: use it.
71
+ * - `"off"`: every trace of it goes. No button in the top bar, no row in the
72
+ * quick actions, no panel, nothing sent. A project that says `false` has
73
+ * decided, and the Studio does not keep offering.
74
+ * - `"unconfigured"`: nobody has said. The affordances are SHOWN, and asking to
75
+ * use the assistant asks to turn it on first. That is the difference between
76
+ * this and `"off"`, and the reason `enabled` is a tri-state: hiding an
77
+ * unconfigured assistant means nobody discovers it, and quietly enabling one
78
+ * means a project starts sending its content to a model because it did not
79
+ * know to say no.
80
+ *
81
+ * A project with NO settings module is `"on"`. There is nowhere to record a
82
+ * decision, so there is nothing to prompt for and nothing to prompt INTO — the
83
+ * prompt writes to a settings module, and that project has none.
84
+ *
85
+ * This is the only answer to "does this project have an assistant":
86
+ * `config.ai.chat` is gone, and with it the chat's title, description and
87
+ * suggestions, which nothing replaces. The AI commit-message summariser is
88
+ * deliberately not part of this — `config.ai.commitMessages` stays where it is,
89
+ * and gets settings of its own later.
90
+ */
91
+ export type AssistantAvailability = "on" | "off" | "unconfigured";
92
+ export declare function assistantAvailability(
93
+ /** The settings module's source, or `undefined` where the project has none. */
94
+ settings: SettingsSource | undefined): AssistantAvailability;