@valbuild/shared 0.150.20 → 0.152.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 +268 -0
- package/client/dist/valbuild-shared-client.cjs.dev.js +1 -1
- package/client/dist/valbuild-shared-client.cjs.prod.js +1 -1
- package/client/dist/valbuild-shared-client.esm.js +1 -1
- package/dist/declarations/src/internal/ApiRoutes.d.ts +270 -2
- package/dist/declarations/src/internal/aiTools/toolNames.d.ts +1 -1
- package/dist/declarations/src/internal/content/applyChain.d.ts +124 -0
- package/dist/declarations/src/internal/content/buildRegistry.d.ts +36 -0
- package/dist/declarations/src/internal/content/jsonValuesOps.d.ts +110 -0
- package/dist/declarations/src/internal/content/moduleStore.d.ts +119 -0
- package/dist/declarations/src/internal/content/source.d.ts +71 -0
- package/dist/declarations/src/internal/content/version.d.ts +151 -0
- package/dist/declarations/src/internal/index.d.ts +8 -1
- package/dist/declarations/src/internal/publish/projectSource.d.ts +28 -0
- package/dist/declarations/src/internal/search/searchIndex.d.ts +34 -3
- package/dist/declarations/src/internal/server/types.d.ts +9 -1
- package/dist/declarations/src/internal/sketchpad/sketchFormat.d.ts +41 -6
- package/dist/declarations/src/internal/valCanvasProtocol.d.ts +46 -0
- package/dist/declarations/src/internal/valClientHeader.d.ts +36 -0
- package/dist/declarations/src/publish/callJson.d.ts +15 -0
- package/dist/declarations/src/publish/jobClient.d.ts +7 -1
- package/dist/declarations/src/publish/publishClient.d.ts +2 -14
- package/dist/{getSitemapTree-fef55872.esm.js → getSitemapTree-3c1b9e57.esm.js} +18 -2
- package/dist/{getSitemapTree-6dfa2f4e.cjs.dev.js → getSitemapTree-59c5ed5d.cjs.dev.js} +18 -2
- package/dist/{getSitemapTree-8a04f3cf.cjs.prod.js → getSitemapTree-6b9024d1.cjs.prod.js} +18 -2
- package/dist/{protocol-d0efb8cd.cjs.dev.js → projectSource-2a58ef15.cjs.dev.js} +221 -0
- package/dist/{protocol-55cfa3cf.esm.js → projectSource-7f0ee83c.esm.js} +216 -1
- package/dist/{protocol-b1055ef0.cjs.prod.js → projectSource-ed6aec39.cjs.prod.js} +221 -0
- package/internal/dist/valbuild-shared-internal.cjs.dev.js +2521 -192
- package/internal/dist/valbuild-shared-internal.cjs.prod.js +2521 -192
- package/internal/dist/valbuild-shared-internal.esm.js +2463 -175
- package/package.json +3 -3
- package/publish/dist/valbuild-shared-publish.cjs.dev.js +48 -155
- package/publish/dist/valbuild-shared-publish.cjs.prod.js +48 -155
- package/publish/dist/valbuild-shared-publish.esm.js +17 -123
- package/dist/declarations/src/internal/server/requestScopedMemo.d.ts +0 -49
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import type { Json, ModuleFilePath, PatchId } from "@valbuild/core";
|
|
2
|
+
import { type ModuleContent } from "./applyChain.js";
|
|
3
|
+
import { type ModuleVersion } from "./version.js";
|
|
4
|
+
/** One module's content at one version, as a tier holds it. */
|
|
5
|
+
export type StoredModule = {
|
|
6
|
+
readonly path: ModuleFilePath;
|
|
7
|
+
readonly version: ModuleVersion;
|
|
8
|
+
readonly content: ModuleContent;
|
|
9
|
+
/**
|
|
10
|
+
* The patch ids the version's chain is made of, in chain order: empty for a
|
|
11
|
+
* base. What a newer version of the same chain is extended from, and what a
|
|
12
|
+
* delta between two versions is made of.
|
|
13
|
+
*/
|
|
14
|
+
readonly patches: readonly PatchId[];
|
|
15
|
+
/**
|
|
16
|
+
* What holding this costs beyond what is held anyway: the containers the
|
|
17
|
+
* engine copied and the values the patches brought, never the base the
|
|
18
|
+
* build holds for every visitor. Zero for the bundle's own module.
|
|
19
|
+
*/
|
|
20
|
+
readonly bytes: number;
|
|
21
|
+
};
|
|
22
|
+
export interface ModuleTier {
|
|
23
|
+
/** `memory`, `bundle`, `base`, `origin`: goes into counted work and test frames. */
|
|
24
|
+
readonly name: string;
|
|
25
|
+
get(key: string): StoredModule | null | Promise<StoredModule | null>;
|
|
26
|
+
put?(key: string, value: StoredModule, bytes: number): void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What the stores in this process have answered, by tier, and how often
|
|
30
|
+
* none of them had a key: a test takes it before and after. Never reset.
|
|
31
|
+
*/
|
|
32
|
+
export declare function storeWork(): {
|
|
33
|
+
hits: Readonly<Record<string, number>>;
|
|
34
|
+
misses: number;
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Asks each tier in turn; what a slower tier answers is written to the faster
|
|
38
|
+
* ones before it. A tier that answers `null` has no say.
|
|
39
|
+
*/
|
|
40
|
+
export declare function tiered(tiers: readonly ModuleTier[]): ModuleTier;
|
|
41
|
+
export type MemoryTier = ModuleTier & {
|
|
42
|
+
put(key: string, value: StoredModule, bytes: number): void;
|
|
43
|
+
/**
|
|
44
|
+
* The newest version held of a module, whatever its key: what the next
|
|
45
|
+
* version of the same chain is extended from rather than replayed.
|
|
46
|
+
*/
|
|
47
|
+
latest(path: ModuleFilePath): StoredModule | null;
|
|
48
|
+
/** What it holds now. */
|
|
49
|
+
stats(): MemoryTierStats;
|
|
50
|
+
/**
|
|
51
|
+
* Charge a held key for something added to it after it was put (an entry
|
|
52
|
+
* resolved on it), so the budget sees it.
|
|
53
|
+
*/
|
|
54
|
+
charge(key: string, bytes: number): void;
|
|
55
|
+
};
|
|
56
|
+
export type MemoryTierStats = {
|
|
57
|
+
/** Bytes held, by the measure each value was put with. */
|
|
58
|
+
bytes: number;
|
|
59
|
+
/** Versions held: values a chain made (not bases written up from a slower tier). */
|
|
60
|
+
versions: number;
|
|
61
|
+
/** Modules with at least one version held. */
|
|
62
|
+
modules: number;
|
|
63
|
+
/** The most versions held of any one module. */
|
|
64
|
+
mostVersionsOfOneModule: number;
|
|
65
|
+
/** The most bytes held for any one module, all its versions together. */
|
|
66
|
+
mostBytesOfOneModule: number;
|
|
67
|
+
/** Values let go so far, for the budget or the ring. */
|
|
68
|
+
evicted: number;
|
|
69
|
+
};
|
|
70
|
+
/**
|
|
71
|
+
* Bounded by bytes; keeps the last `lastPerModule` versions per module path
|
|
72
|
+
* (and, apart from them, the last `lastPerModule` bases), and lets go of the least recently read value when over `budgetBytes`. A
|
|
73
|
+
* value bigger than the whole budget is still held while it is the newest,
|
|
74
|
+
* so the read that made it is answered; the next put lets it go.
|
|
75
|
+
*/
|
|
76
|
+
export declare function memoryTier(opts: {
|
|
77
|
+
budgetBytes: number;
|
|
78
|
+
lastPerModule: number;
|
|
79
|
+
name?: string;
|
|
80
|
+
}): MemoryTier;
|
|
81
|
+
/**
|
|
82
|
+
* The build's own modules, free: answers only a key whose version is the
|
|
83
|
+
* bundle's -- its schema, its base and the empty chain -- with the bundle's
|
|
84
|
+
* own object, which the process holds for every visitor anyway. `bundle`
|
|
85
|
+
* says what the build evaluated each module to; `null` for a module the
|
|
86
|
+
* build does not have.
|
|
87
|
+
*/
|
|
88
|
+
export declare function bundleTier(bundle: (path: ModuleFilePath) => {
|
|
89
|
+
version: ModuleVersion;
|
|
90
|
+
source: Json;
|
|
91
|
+
} | null | Promise<{
|
|
92
|
+
version: ModuleVersion;
|
|
93
|
+
source: Json;
|
|
94
|
+
} | null>, name?: string): ModuleTier;
|
|
95
|
+
/** The module a {@link moduleKey} is the address of, or `null` for none. */
|
|
96
|
+
export declare function pathOfModuleKey(key: string): ModuleFilePath | null;
|
|
97
|
+
/**
|
|
98
|
+
* The version a {@link moduleKey} names, or `null` when it is not one. The
|
|
99
|
+
* three parts are hex or empty, so the split is unambiguous.
|
|
100
|
+
*/
|
|
101
|
+
export declare function parseModuleKey(key: string): {
|
|
102
|
+
path: ModuleFilePath;
|
|
103
|
+
version: ModuleVersion;
|
|
104
|
+
} | null;
|
|
105
|
+
/**
|
|
106
|
+
* A `.jsonValues()` entry's version: the record's schema and base, the
|
|
107
|
+
* entry's key, and the chain of the record's patches that touched THIS
|
|
108
|
+
* entry -- so an entry no patch touched keeps its version while the record's
|
|
109
|
+
* chain grows, and two reads at one entry version are the same content.
|
|
110
|
+
* Entries are stored on their own, so a large record is not copied because
|
|
111
|
+
* one entry changed.
|
|
112
|
+
*/
|
|
113
|
+
export type EntryVersion = ModuleVersion & {
|
|
114
|
+
key: string;
|
|
115
|
+
};
|
|
116
|
+
/** The address of an entry's content. Never changes meaning. */
|
|
117
|
+
export declare function entryAddress(path: ModuleFilePath, v: EntryVersion): string;
|
|
118
|
+
/** Whether two entry versions name the same content. */
|
|
119
|
+
export declare function sameEntryVersion(a: EntryVersion, b: EntryVersion): boolean;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { Json, ModuleFilePath } from "@valbuild/core";
|
|
2
|
+
import type { ModuleVersion } from "./version.js";
|
|
3
|
+
import type { EntryVersion } from "./moduleStore.js";
|
|
4
|
+
/**
|
|
5
|
+
* Where a reader's content comes from, in one shape: see
|
|
6
|
+
* `docs/plans/one-content-model.md`, "One way to load". Every reader --
|
|
7
|
+
* `fetchVal` on the server, `useVal` in the browser, and (phase 5) the
|
|
8
|
+
* Studio -- asks a list of these, and only the list differs between them.
|
|
9
|
+
*
|
|
10
|
+
* Types only, here, because the sources are written on both sides of a
|
|
11
|
+
* package line: the browser's (`@valbuild/react`) and the server's
|
|
12
|
+
* (`@valbuild/server`, which cannot import React's package), and the one
|
|
13
|
+
* rule that picks between them (`layered`) has to read both.
|
|
14
|
+
*/
|
|
15
|
+
/** What a source says about one module. */
|
|
16
|
+
export type ModuleRead = {
|
|
17
|
+
status: "ready";
|
|
18
|
+
source: Json;
|
|
19
|
+
/** Which source answered: goes into a reader's reads, and test frames. */
|
|
20
|
+
from: string;
|
|
21
|
+
/** The version of what it answered, where the source carries one. */
|
|
22
|
+
version: ModuleVersion | null;
|
|
23
|
+
}
|
|
24
|
+
/** No say here: ask the next source. */
|
|
25
|
+
| {
|
|
26
|
+
status: "pass";
|
|
27
|
+
}
|
|
28
|
+
/** Will have a say: wait for `ready`, then ask again. */
|
|
29
|
+
| {
|
|
30
|
+
status: "pending";
|
|
31
|
+
ready: Promise<void>;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* What a source says about one `.jsonValues()` entry: the same as a module,
|
|
35
|
+
* plus `gone` -- an ANSWER, "this entry is not there" (a pending patch removed
|
|
36
|
+
* it), which is not the same as `pass`, "I cannot say". Collapsing the two is
|
|
37
|
+
* what once made a deleted entry keep rendering its published content.
|
|
38
|
+
*/
|
|
39
|
+
export type EntryRead = {
|
|
40
|
+
status: "ready";
|
|
41
|
+
content: unknown;
|
|
42
|
+
from: string;
|
|
43
|
+
/** The entry's own version, where the source carries one. */
|
|
44
|
+
version?: EntryVersion | null;
|
|
45
|
+
} | {
|
|
46
|
+
status: "gone";
|
|
47
|
+
from: string;
|
|
48
|
+
} | {
|
|
49
|
+
status: "pass";
|
|
50
|
+
} | {
|
|
51
|
+
status: "pending";
|
|
52
|
+
ready: Promise<void>;
|
|
53
|
+
}
|
|
54
|
+
/** The source tried and could not: an entry's import that rejected. */
|
|
55
|
+
| {
|
|
56
|
+
status: "failed";
|
|
57
|
+
error: unknown;
|
|
58
|
+
};
|
|
59
|
+
export interface ContentSource {
|
|
60
|
+
/** `overlay`, `server-draft`, `snapshot`, `bundle`: what `from` says. */
|
|
61
|
+
readonly name: string;
|
|
62
|
+
module(path: ModuleFilePath): ModuleRead;
|
|
63
|
+
entry(path: ModuleFilePath, key: string): EntryRead;
|
|
64
|
+
/** Call `onChange` when a read of `paths` may answer differently. */
|
|
65
|
+
subscribe?(paths: readonly ModuleFilePath[], onChange: () => void): () => void;
|
|
66
|
+
/**
|
|
67
|
+
* A string that changes exactly when a read of `paths` would: what a hook
|
|
68
|
+
* hands `useSyncExternalStore`, compared with `===`.
|
|
69
|
+
*/
|
|
70
|
+
revision?(paths: readonly ModuleFilePath[]): string;
|
|
71
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { type ModuleFilePath, type PatchId } from "@valbuild/core";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
/**
|
|
4
|
+
* What a module's content IS: the schema it is read and patched with, the
|
|
5
|
+
* base it starts from, and the chain of patches on top. Three parts because
|
|
6
|
+
* each kind of change moves exactly one of them (see
|
|
7
|
+
* `docs/plans/one-content-model.md`, "A version, per module, with the code in
|
|
8
|
+
* it"):
|
|
9
|
+
*
|
|
10
|
+
* - a schema change, or a new engine (`@valbuild/*` upgraded), moves `schema`;
|
|
11
|
+
* - a publish, a content version, a literal edited in code, moves `base`;
|
|
12
|
+
* - an edit, a discard, a publish (which empties the chain) moves `chain`.
|
|
13
|
+
*
|
|
14
|
+
* Two answers with the same version are the same content, wherever they came
|
|
15
|
+
* from. That is the whole point: comparing contents becomes comparing three
|
|
16
|
+
* strings.
|
|
17
|
+
*
|
|
18
|
+
* Computed when content CHANGES -- a module evaluated, a base adopted, a patch
|
|
19
|
+
* appended -- never when it is read (risk 3 in the plan): every function here
|
|
20
|
+
* that hashes counts itself ({@link versionHashCount}), so a reader that
|
|
21
|
+
* hashes is caught by a counted budget rather than by a profile.
|
|
22
|
+
*/
|
|
23
|
+
export type ModuleVersion = {
|
|
24
|
+
/** sha(engine + serialized schema): what the content is read and patched with. */
|
|
25
|
+
schema: string;
|
|
26
|
+
/** sha(the module's base source): from the build, a content version or a publish. */
|
|
27
|
+
base: string;
|
|
28
|
+
/**
|
|
29
|
+
* A fold of this module's patch ids on top of `base`, in chain order: the
|
|
30
|
+
* IDENTITY of the chain, not its order -- that is `seq`. {@link EMPTY_CHAIN}
|
|
31
|
+
* for no patches, so a base is a version with an empty chain.
|
|
32
|
+
*/
|
|
33
|
+
chain: string;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* What a project answer is relative to: which build served it, and how far
|
|
37
|
+
* along the project's changes it is.
|
|
38
|
+
*/
|
|
39
|
+
export type ProjectVersion = {
|
|
40
|
+
/**
|
|
41
|
+
* The build serving it: the evaluated modules (schemas, sources and config)
|
|
42
|
+
* it started from, so two processes running the same code agree. Hosted,
|
|
43
|
+
* the commit or the platform's build (`publishBuild`) is part of it.
|
|
44
|
+
*/
|
|
45
|
+
build: string;
|
|
46
|
+
/**
|
|
47
|
+
* ORDER only: bumped by every write, delete and publish, and once by each
|
|
48
|
+
* new build. Of two answers, the one with the higher `seq` is newer. In
|
|
49
|
+
* http mode it is the content service's `headVersion`.
|
|
50
|
+
*/
|
|
51
|
+
seq: number;
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* What the schema version of a module staged after the build serving a
|
|
55
|
+
* proposal starts with: `staged:<inputs>`. Val's server has not got that
|
|
56
|
+
* module's schema, so it names the staging instead; a Studio compares no
|
|
57
|
+
* build's code against it.
|
|
58
|
+
*/
|
|
59
|
+
export declare const STAGED_SCHEMA_PREFIX = "staged:";
|
|
60
|
+
/** The chain of no patches. */
|
|
61
|
+
export declare const EMPTY_CHAIN = "";
|
|
62
|
+
/**
|
|
63
|
+
* The engine: how patches are applied and content is read. Part of every
|
|
64
|
+
* schema version, because content read by another engine is not the same
|
|
65
|
+
* content even under the same schema.
|
|
66
|
+
*/
|
|
67
|
+
export declare const ENGINE: string;
|
|
68
|
+
/**
|
|
69
|
+
* How many times a version has been hashed in this process: the counted
|
|
70
|
+
* budget behind "versions are computed when content changes, never when it
|
|
71
|
+
* is read" (`version:hash`). A test takes it before and after a read.
|
|
72
|
+
*/
|
|
73
|
+
export declare function versionHashCount(): number;
|
|
74
|
+
/** A chain one patch longer. */
|
|
75
|
+
export declare function extendChain(prev: string, id: PatchId): string;
|
|
76
|
+
/** The chain of these patch ids, in order, on top of a base. */
|
|
77
|
+
export declare function chainOf(ids: readonly PatchId[]): string;
|
|
78
|
+
/**
|
|
79
|
+
* A module's schema version, from its serialized schema. The same function
|
|
80
|
+
* on the server (from what `extractValModules` serialized) and in the Studio
|
|
81
|
+
* (from what it holds in `SchemaStore`), so the two compare.
|
|
82
|
+
*/
|
|
83
|
+
export declare function schemaVersionOf(serializedSchema: unknown): string;
|
|
84
|
+
/**
|
|
85
|
+
* A module's base version, from its base source: what the module evaluates
|
|
86
|
+
* to, or what a content version or a publish put in its place. `.jsonValues()`
|
|
87
|
+
* entries are markers here; an entry's own version comes with entries stored
|
|
88
|
+
* as modules of their own (phase 6).
|
|
89
|
+
*/
|
|
90
|
+
export declare function baseVersionOf(source: unknown): string;
|
|
91
|
+
/** The address of a module's content. Never changes meaning. */
|
|
92
|
+
export declare function moduleKey(path: ModuleFilePath, v: ModuleVersion): string;
|
|
93
|
+
/** Whether two versions name the same content. */
|
|
94
|
+
export declare function sameModuleVersion(a: ModuleVersion, b: ModuleVersion): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* The chains of every module, from the patches a draft applies (in chain
|
|
97
|
+
* order), folded incrementally.
|
|
98
|
+
*
|
|
99
|
+
* A read asks for the chains every time; the fold is extended only by the
|
|
100
|
+
* patches the chain has gained since the last call, and a call that finds the
|
|
101
|
+
* chain as it was hashes nothing. A chain that changed anywhere but at its end
|
|
102
|
+
* (a discard in the middle, a publish) is folded again from the start --
|
|
103
|
+
* once, when that is first noticed.
|
|
104
|
+
*/
|
|
105
|
+
export declare class ChainFold {
|
|
106
|
+
private ids;
|
|
107
|
+
private paths;
|
|
108
|
+
private chains;
|
|
109
|
+
/**
|
|
110
|
+
* Every module's chain after each of its patches, in order: what
|
|
111
|
+
* {@link chainAt} answers for a module whose content stopped part way
|
|
112
|
+
* along its chain (a patch that does not apply). Kept as the chains are
|
|
113
|
+
* extended, so asking hashes nothing.
|
|
114
|
+
*/
|
|
115
|
+
private prefixes;
|
|
116
|
+
/**
|
|
117
|
+
* Bumped whenever the chains move: a cheap identity for "the same chains as
|
|
118
|
+
* before", in-process. With {@link instance} in front, two folds never name
|
|
119
|
+
* one epoch: two processes (or isolates) holding the same chains answer
|
|
120
|
+
* differently, which only costs a cache a miss, where two holding
|
|
121
|
+
* DIFFERENT chains at the same count must never answer alike (an ETag
|
|
122
|
+
* answered 304 for content the caller does not have).
|
|
123
|
+
*/
|
|
124
|
+
private epochValue;
|
|
125
|
+
private readonly instance;
|
|
126
|
+
/** Every module's chain, for these patches. Modules with none are absent. */
|
|
127
|
+
chainsOf(patches: readonly {
|
|
128
|
+
patchId: PatchId;
|
|
129
|
+
path: ModuleFilePath;
|
|
130
|
+
}[]): ReadonlyMap<ModuleFilePath, string>;
|
|
131
|
+
/**
|
|
132
|
+
* A module's chain after its first `count` patches, of those the last
|
|
133
|
+
* {@link chainsOf} was given: the version of content that stopped at a
|
|
134
|
+
* patch that does not apply, so the version names the chain the content
|
|
135
|
+
* is AT. Hashes nothing.
|
|
136
|
+
*/
|
|
137
|
+
chainAt(path: ModuleFilePath, count: number): string;
|
|
138
|
+
/** See {@link epochValue}. */
|
|
139
|
+
epoch(): string;
|
|
140
|
+
}
|
|
141
|
+
/** {@link ModuleVersion} on the wire. */
|
|
142
|
+
export declare const ModuleVersionSchema: z.ZodObject<{
|
|
143
|
+
schema: z.ZodString;
|
|
144
|
+
base: z.ZodString;
|
|
145
|
+
chain: z.ZodString;
|
|
146
|
+
}, z.core.$strip>;
|
|
147
|
+
/** {@link ProjectVersion} on the wire. */
|
|
148
|
+
export declare const ProjectVersionSchema: z.ZodObject<{
|
|
149
|
+
build: z.ZodString;
|
|
150
|
+
seq: z.ZodNumber;
|
|
151
|
+
}, z.core.$strip>;
|
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
export * from "./richtext/conversion/index.js";
|
|
2
2
|
export * from "./richtext/richTextImages.js";
|
|
3
3
|
export * from "./server/types.js";
|
|
4
|
-
export * from "./server/requestScopedMemo.js";
|
|
5
4
|
export * from "./ValClient.js";
|
|
6
5
|
export * from "./demo.js";
|
|
7
6
|
export * from "./ValUrls.js";
|
|
8
7
|
export * from "./ApiRoutes.js";
|
|
8
|
+
export * from "./valClientHeader.js";
|
|
9
9
|
export * from "./schema/compatibility.js";
|
|
10
10
|
export * from "./newestCommitSha.js";
|
|
11
11
|
export * from "./chainHead.js";
|
|
12
|
+
export * from "./content/version.js";
|
|
13
|
+
export * from "./content/buildRegistry.js";
|
|
14
|
+
export * from "./content/applyChain.js";
|
|
15
|
+
export * from "./content/jsonValuesOps.js";
|
|
16
|
+
export * from "./content/moduleStore.js";
|
|
17
|
+
export type * from "./content/source.js";
|
|
12
18
|
export * from "./zod/Patch.js";
|
|
13
19
|
export * from "./sessionStorage.js";
|
|
14
20
|
export * from "./SharedValConfig.js";
|
|
@@ -46,6 +52,7 @@ export * from "./search/sourcePath.js";
|
|
|
46
52
|
export * from "./theme/accentRamp.js";
|
|
47
53
|
export * from "./publish/contentApi.js";
|
|
48
54
|
export * from "./publish/protocol.js";
|
|
55
|
+
export * from "./publish/projectSource.js";
|
|
49
56
|
export * from "./patchSets/PatchSets.js";
|
|
50
57
|
export * from "./patchSets/selectionClosure.js";
|
|
51
58
|
export * from "./patchSets/schemaTypesOfPath.js";
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where content's publish API is reached: through Val's proxy on this origin
|
|
3
|
+
* (`api`, the Studio), or directly (`publishApi`, `<content>/v1`: home's
|
|
4
|
+
* upgrade page, whose `fetchImpl` adds the project token).
|
|
5
|
+
*/
|
|
6
|
+
export type PublishApiAt = {
|
|
7
|
+
/** Val's API root on this origin, e.g. `/api/val`. */
|
|
8
|
+
api: string;
|
|
9
|
+
} | {
|
|
10
|
+
/** Content's publish API itself, e.g. `https://content.val.build/v1`. */
|
|
11
|
+
publishApi: string;
|
|
12
|
+
};
|
|
13
|
+
export declare function publishApiOf(at: PublishApiAt): string;
|
|
14
|
+
/**
|
|
15
|
+
* The site's live source, BY VERSION: for a reader that asks again and again
|
|
16
|
+
* over a long life -- the Studio's code tools, on every tool call -- rather
|
|
17
|
+
* than once per deploy, as {@link createStudioPublishClient}'s memo does.
|
|
18
|
+
*
|
|
19
|
+
* Each call asks `/project-source` with the `ETag` of the answer it holds
|
|
20
|
+
* (`If-None-Match`), and content answers 304 while it is still that one
|
|
21
|
+
* (valbuild/home sends the tag: the sha256 of its answer). So a publish in
|
|
22
|
+
* between is seen on the next call, and the whole source is sent only when
|
|
23
|
+
* it changed. An answer without a tag (an older content service) is not
|
|
24
|
+
* held: every call reads it again, which is right, if dearer.
|
|
25
|
+
*/
|
|
26
|
+
export declare function createProjectSourceReader(options: PublishApiAt & {
|
|
27
|
+
fetchImpl?: typeof fetch;
|
|
28
|
+
}): () => Promise<Record<string, string> | null>;
|
|
@@ -57,6 +57,28 @@ export type SearchIndex = {
|
|
|
57
57
|
* re-indexing one module exists to avoid.
|
|
58
58
|
*/
|
|
59
59
|
docsByModule: Map<ModuleFilePath, Set<string>>;
|
|
60
|
+
/**
|
|
61
|
+
* For a module whose root is a record: each entry's documents, and the
|
|
62
|
+
* value they were made from, by key -- so indexing it again walks only the
|
|
63
|
+
* entries whose value is a different object (see {@link indexModule}).
|
|
64
|
+
*/
|
|
65
|
+
/**
|
|
66
|
+
* What each module was last indexed from, whole: the same source object
|
|
67
|
+
* under a schema that says the same is not walked again. Removing a
|
|
68
|
+
* document from a big index is slow (FlexSearch looks for it everywhere),
|
|
69
|
+
* so indexing an unchanged module again was the cost of a pass.
|
|
70
|
+
*/
|
|
71
|
+
walkedByModule: Map<ModuleFilePath, {
|
|
72
|
+
source: Json;
|
|
73
|
+
schema: string;
|
|
74
|
+
}>;
|
|
75
|
+
entriesByModule: Map<ModuleFilePath, {
|
|
76
|
+
schema: SerializedSchema;
|
|
77
|
+
entries: Map<string, {
|
|
78
|
+
source: Json;
|
|
79
|
+
docs: string[];
|
|
80
|
+
}>;
|
|
81
|
+
}>;
|
|
60
82
|
};
|
|
61
83
|
/** An index with nothing in it. Fill it with {@link indexModule}. */
|
|
62
84
|
export declare function createSearchIndex(): SearchIndex;
|
|
@@ -70,10 +92,19 @@ export declare function createSearchIndex(): SearchIndex;
|
|
|
70
92
|
* `.jsonValues()` entries are indexed only once their content is loaded — an
|
|
71
93
|
* un-loaded entry is an opaque marker that {@link traverseSchemaSource} skips, so
|
|
72
94
|
* the index is naturally PARTIAL and grows as batches land. That is why the search
|
|
73
|
-
* UI loads the entries on the first query and re-indexes
|
|
74
|
-
*
|
|
95
|
+
* UI loads the entries on the first query and re-indexes as they arrive, rather
|
|
96
|
+
* than pretending an empty result set is an answer -- and why a module whose
|
|
97
|
+
* root is a record is re-indexed by entry ({@link indexRecordModule}): a pass
|
|
98
|
+
* per batch walks the batch, not the record.
|
|
99
|
+
*/
|
|
100
|
+
export declare function indexModule(searchIndex: SearchIndex, moduleFilePath: ModuleFilePath, source: Json, schema: SerializedSchema,
|
|
101
|
+
/**
|
|
102
|
+
* Stop by this time (`performance.now()`), for a caller that must not hold
|
|
103
|
+
* the thread: answers `false` when it stopped with entries of a record
|
|
104
|
+
* module still to walk, and the same call again goes on from there. A
|
|
105
|
+
* module that is not a record is walked whole, whatever the time.
|
|
75
106
|
*/
|
|
76
|
-
|
|
107
|
+
until?: number): boolean;
|
|
77
108
|
/**
|
|
78
109
|
* Drop everything indexed for one module.
|
|
79
110
|
*
|
|
@@ -45,7 +45,15 @@ export type ValServerJsonResult<Body extends Json | never = never, Error extends
|
|
|
45
45
|
status: 400;
|
|
46
46
|
json: Error;
|
|
47
47
|
} : never);
|
|
48
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Nothing moved since the ETag the caller sent back: no body, as HTTP
|
|
50
|
+
* requires of a 304.
|
|
51
|
+
*/
|
|
52
|
+
export type ValServerNotModified = {
|
|
53
|
+
status: 304;
|
|
54
|
+
headers?: Record<string, string | undefined>;
|
|
55
|
+
};
|
|
56
|
+
export type ValServerGenericResult = ValServerNotModified | ValServerJsonResult<Json> | ValServerError | ValServerRedirectResult<ValCookiesNames> | ValServerResult<ValCookiesNames, string | ReadableStream<Uint8Array>>;
|
|
49
57
|
export type ValUIRequestHandler = (path: string, url: string) => Promise<ValServerGenericResult>;
|
|
50
58
|
export type ValSession = {
|
|
51
59
|
mode: "unauthorized";
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
* A page sketch: a page written as indented text, with four primitives.
|
|
3
3
|
*
|
|
4
4
|
* ```
|
|
5
|
+
* --- /about a route: what follows is the page at /about
|
|
5
6
|
* # Our people text (a heading: #, ## or ###)
|
|
6
7
|
* We are 40 people in Oslo. text (a paragraph)
|
|
8
|
+
* ## Hi {field: /x.val.ts?p="t"} text linked to a content field: typing
|
|
9
|
+
* in it writes the field
|
|
7
10
|
* [image: Team photo] placeholder: [kind] or [kind: label]
|
|
8
11
|
* [hero: Roof](image: /a.jpg) a placeholder with an image (or video) in it
|
|
9
12
|
* icon hero M3 5h18v14H3z how every [hero] box is drawn: SVG path data
|
|
10
13
|
* row its children side by side
|
|
11
|
-
* col
|
|
12
|
-
* ## Design
|
|
14
|
+
* col its children stacked; columns share a row
|
|
15
|
+
* ## Design equally
|
|
13
16
|
* col
|
|
14
17
|
* [button: Join us]
|
|
15
18
|
* ```
|
|
16
19
|
*
|
|
20
|
+
* A sketchpad is not one page's: a route divider starts the part about a
|
|
21
|
+
* page, so one sketch can cover a page and the two it links to.
|
|
22
|
+
*
|
|
17
23
|
* It is a scratch pad, not a layout language: it says WHAT is on a page and
|
|
18
24
|
* roughly WHERE, so an assistant and a person can agree on a page's content
|
|
19
25
|
* before anyone worries about how it looks. The person never sees this text,
|
|
@@ -31,17 +37,23 @@
|
|
|
31
37
|
* image. Media put in a box is written into the text too, so the assistant
|
|
32
38
|
* that builds the page reads it exactly as the person left it.
|
|
33
39
|
*/
|
|
34
|
-
export type SketchNode = SketchRow | SketchCol | SketchText | SketchPlaceholder;
|
|
40
|
+
export type SketchNode = SketchRoute | SketchRow | SketchCol | SketchText | SketchPlaceholder;
|
|
41
|
+
/** The start of the part about one page. Only at the top level. */
|
|
42
|
+
export type SketchRoute = {
|
|
43
|
+
type: "route";
|
|
44
|
+
line: number;
|
|
45
|
+
/** The page's route as the site serves it: `/about`, `/blog/[slug]`. */
|
|
46
|
+
route: string;
|
|
47
|
+
};
|
|
35
48
|
export type SketchRow = {
|
|
36
49
|
type: "row";
|
|
37
50
|
line: number;
|
|
38
51
|
children: SketchNode[];
|
|
39
52
|
};
|
|
53
|
+
/** Columns share their row equally: a sketch says what, not how wide. */
|
|
40
54
|
export type SketchCol = {
|
|
41
55
|
type: "col";
|
|
42
56
|
line: number;
|
|
43
|
-
/** Shares of the enclosing row this column takes. */
|
|
44
|
-
weight: number;
|
|
45
57
|
children: SketchNode[];
|
|
46
58
|
};
|
|
47
59
|
export type SketchText = {
|
|
@@ -50,6 +62,13 @@ export type SketchText = {
|
|
|
50
62
|
/** 0 is a paragraph; 1-3 are headings. */
|
|
51
63
|
level: 0 | 1 | 2 | 3;
|
|
52
64
|
text: string;
|
|
65
|
+
/**
|
|
66
|
+
* The content field this text is linked to, as a source path
|
|
67
|
+
* (`/content/home.val.ts?p="hero"."title"`): typing in the block writes
|
|
68
|
+
* the field. Written `{field: <source path>}` at the end of the line. One
|
|
69
|
+
* block per field: a second link to the same field is not one.
|
|
70
|
+
*/
|
|
71
|
+
field?: string;
|
|
53
72
|
};
|
|
54
73
|
export type SketchPlaceholder = {
|
|
55
74
|
type: "placeholder";
|
|
@@ -58,10 +77,18 @@ export type SketchPlaceholder = {
|
|
|
58
77
|
label: string | null;
|
|
59
78
|
media: SketchMedia | null;
|
|
60
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* `src` names a file the project has, never a guess: its `path` exactly as
|
|
82
|
+
* the content has it (`/public/val/hero.webp`, or its remote URL), or an
|
|
83
|
+
* image uploaded to the assistant's session, `session:<key>`. Which files
|
|
84
|
+
* those are is the Studio's to check; the text only holds the name.
|
|
85
|
+
*/
|
|
61
86
|
export type SketchMedia = {
|
|
62
87
|
type: "image" | "video";
|
|
63
88
|
src: string;
|
|
64
89
|
};
|
|
90
|
+
/** The prefix of a `src` that is an image in the assistant's session. */
|
|
91
|
+
export declare const SESSION_MEDIA_PREFIX = "session:";
|
|
65
92
|
export type SketchError = {
|
|
66
93
|
/** 1-based, like an editor's gutter. */
|
|
67
94
|
line: number;
|
|
@@ -82,7 +109,7 @@ export declare function parseSketch(source: string): ParsedSketch;
|
|
|
82
109
|
* The line a text block is written as. Line breaks become spaces, and a
|
|
83
110
|
* paragraph that would read as something else is kept as text with `\`.
|
|
84
111
|
*/
|
|
85
|
-
export declare function formatSketchText(level: SketchText["level"], text: string): string;
|
|
112
|
+
export declare function formatSketchText(level: SketchText["level"], text: string, field?: string): string;
|
|
86
113
|
/** The line a placeholder is written as. */
|
|
87
114
|
export declare function formatSketchBox(placeholder: Pick<SketchPlaceholder, "kind" | "label" | "media">): string;
|
|
88
115
|
/**
|
|
@@ -95,6 +122,14 @@ export declare function formatSketchBox(placeholder: Pick<SketchPlaceholder, "ki
|
|
|
95
122
|
* it is not on the page.
|
|
96
123
|
*/
|
|
97
124
|
export declare function formatSketch(blocks: SketchNode[], icons: Record<string, string>): string;
|
|
125
|
+
/**
|
|
126
|
+
* The text of every block linked to a field, by field: what the Studio
|
|
127
|
+
* writes to the content when a linked block changes. The first block wins,
|
|
128
|
+
* as `formatSketch` keeps only its link.
|
|
129
|
+
*/
|
|
130
|
+
export declare function sketchFieldTexts(blocks: SketchNode[]): Map<string, string>;
|
|
131
|
+
/** The pages a sketch is about, in the order it gives them. */
|
|
132
|
+
export declare function sketchRoutes(blocks: SketchNode[]): string[];
|
|
98
133
|
/**
|
|
99
134
|
* What a list names a sketch by: its first top-level heading, the biggest
|
|
100
135
|
* one first. Null for a sketch with no heading, which a list then names by
|
|
@@ -93,6 +93,26 @@ export type ValCanvasPageMessage = {
|
|
|
93
93
|
draftMode: boolean;
|
|
94
94
|
/** The URL the frame ended up on, which a redirect may have changed. */
|
|
95
95
|
url: string;
|
|
96
|
+
/**
|
|
97
|
+
* The modules the page holds a draft of: what its server read for this
|
|
98
|
+
* request (`<ValProvider draft>`). The studio sends each of them in its
|
|
99
|
+
* catch-up, pending or not, so a draft that stopped being the draft
|
|
100
|
+
* before the studio was there to say so -- discarded, published -- is
|
|
101
|
+
* replaced rather than kept. Absent from a page of the release before,
|
|
102
|
+
* and from a page that read no draft.
|
|
103
|
+
*/
|
|
104
|
+
holds?: ModuleFilePath[];
|
|
105
|
+
/**
|
|
106
|
+
* The version the page holds each of {@link holds} at, where its draft
|
|
107
|
+
* says (`draftHoldsVersions`): the studio does not send one it would
|
|
108
|
+
* send at the same version -- the same content. Absent from a page of
|
|
109
|
+
* the release before, which is sent everything it holds, as before.
|
|
110
|
+
*/
|
|
111
|
+
holdsVersions?: Partial<Record<ModuleFilePath, {
|
|
112
|
+
schema: string;
|
|
113
|
+
base: string;
|
|
114
|
+
chain: string;
|
|
115
|
+
}>>;
|
|
96
116
|
} | {
|
|
97
117
|
val: typeof VAL_CANVAS_MESSAGE;
|
|
98
118
|
type: "elements";
|
|
@@ -257,8 +277,18 @@ export type ValCanvasStudioMessage = {
|
|
|
257
277
|
/**
|
|
258
278
|
* The whole module source. `unknown` because the page's own types are the
|
|
259
279
|
* ones that give it meaning; nothing in between should be interpreting it.
|
|
280
|
+
*
|
|
281
|
+
* @deprecated A page of this release reads `read`; this is for a page
|
|
282
|
+
* of the release before. Goes in the release after the one that ships
|
|
283
|
+
* `read`, with the bridges' fallback that reads it: see
|
|
284
|
+
* "Release-delayed deletions" in `docs/plans/one-content-model.md`.
|
|
260
285
|
*/
|
|
261
286
|
source: unknown;
|
|
287
|
+
/**
|
|
288
|
+
* The same module as a read (`ModuleRead`), which the page keeps as it
|
|
289
|
+
* is. Optional: a Studio from the release before sends `source` alone.
|
|
290
|
+
*/
|
|
291
|
+
read?: unknown;
|
|
262
292
|
} | {
|
|
263
293
|
/**
|
|
264
294
|
* Everything the studio holds has been sent.
|
|
@@ -276,6 +306,22 @@ export type ValCanvasStudioMessage = {
|
|
|
276
306
|
*/
|
|
277
307
|
val: typeof VAL_CANVAS_MESSAGE;
|
|
278
308
|
type: "sourcesSynced";
|
|
309
|
+
} | {
|
|
310
|
+
/**
|
|
311
|
+
* The server holds the editor's changes up to this version (`seq`, the
|
|
312
|
+
* server's `ProjectVersion.seq`; `null` from a server that reports
|
|
313
|
+
* none). Sent once after `sourcesSynced`, then every time it moves.
|
|
314
|
+
*
|
|
315
|
+
* What the page re-reads its server-rendered content on (a loader, a
|
|
316
|
+
* server component): those only show an edit once the server has it,
|
|
317
|
+
* so a refresh sent when the edit was typed raced the write and came
|
|
318
|
+
* back without it -- which a ten second safety refresh used to catch.
|
|
319
|
+
* A bridge of the release before ignores it, as it ignores any type it
|
|
320
|
+
* does not know.
|
|
321
|
+
*/
|
|
322
|
+
val: typeof VAL_CANVAS_MESSAGE;
|
|
323
|
+
type: "confirmed";
|
|
324
|
+
seq: number | null;
|
|
279
325
|
} | {
|
|
280
326
|
/**
|
|
281
327
|
* What colour to outline the page's content in.
|