@valbuild/shared 0.150.20 → 0.151.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 +236 -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 +253 -2
- 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/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 +2387 -189
- package/internal/dist/valbuild-shared-internal.cjs.prod.js +2387 -189
- package/internal/dist/valbuild-shared-internal.esm.js +2332 -172
- 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,124 @@
|
|
|
1
|
+
import { type PatchId } from "@valbuild/core";
|
|
2
|
+
import type { SerializedSchema } from "@valbuild/core";
|
|
3
|
+
import { type Operation, type Patch, type ReadonlyJSONValue } from "@valbuild/core/patch";
|
|
4
|
+
/** What one `.jsonValues()` entry is at a point in a module's chain. */
|
|
5
|
+
export type EntryState =
|
|
6
|
+
/** Its content, known. */
|
|
7
|
+
{
|
|
8
|
+
kind: "content";
|
|
9
|
+
content: ReadonlyJSONValue;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The content of the BASE entry `from` (its own key, or the one it was
|
|
13
|
+
* moved or copied from), with `ops` on top: an entry nobody has loaded,
|
|
14
|
+
* whose edits are held until somebody does ({@link resolveEntry}).
|
|
15
|
+
*/
|
|
16
|
+
| {
|
|
17
|
+
kind: "waiting";
|
|
18
|
+
from: string;
|
|
19
|
+
ops: HeldOps | null;
|
|
20
|
+
}
|
|
21
|
+
/** Removed, or moved away. */
|
|
22
|
+
| {
|
|
23
|
+
kind: "deleted";
|
|
24
|
+
}
|
|
25
|
+
/** An op on it could not be applied. Sticky, as a module's failure is. */
|
|
26
|
+
| {
|
|
27
|
+
kind: "error";
|
|
28
|
+
patchId: PatchId;
|
|
29
|
+
message: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The ops held for an entry, newest first: a list that is extended without
|
|
33
|
+
* copying, so a chain of N edits to an entry nobody loaded costs N, not N².
|
|
34
|
+
*/
|
|
35
|
+
export type HeldOps = {
|
|
36
|
+
readonly op: Operation;
|
|
37
|
+
readonly patchId: PatchId;
|
|
38
|
+
readonly before: HeldOps | null;
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* A module's content: its source, and what its `.jsonValues()` entries are.
|
|
42
|
+
*
|
|
43
|
+
* `entries` holds only the entries the chain has touched. An entry it has
|
|
44
|
+
* not is the base's: `waiting` on its own key with nothing held, as long as
|
|
45
|
+
* the source has its marker ({@link entryState}). So a module with no
|
|
46
|
+
* entries, or a chain that never touches one, costs nothing here.
|
|
47
|
+
*/
|
|
48
|
+
export type ModuleContent = {
|
|
49
|
+
readonly source: ReadonlyJSONValue;
|
|
50
|
+
readonly entries: ReadonlyMap<string, EntryState>;
|
|
51
|
+
};
|
|
52
|
+
/** One patch of a module's chain. */
|
|
53
|
+
export type PatchRecord = {
|
|
54
|
+
readonly patchId: PatchId;
|
|
55
|
+
readonly patch: Patch;
|
|
56
|
+
};
|
|
57
|
+
export type PatchRecordResult = {
|
|
58
|
+
status: "applied";
|
|
59
|
+
module: ModuleContent;
|
|
60
|
+
} | {
|
|
61
|
+
status: "failed";
|
|
62
|
+
patchId: PatchId;
|
|
63
|
+
message: string;
|
|
64
|
+
};
|
|
65
|
+
/** A module's content with nothing applied yet: its base. */
|
|
66
|
+
export declare function baseModuleContent(source: ReadonlyJSONValue): ModuleContent;
|
|
67
|
+
/**
|
|
68
|
+
* The work the engine has done in this process: patches applied
|
|
69
|
+
* (`engine:apply-patch`) and bytes copied (`engine:clone-bytes`, see
|
|
70
|
+
* {@link SLOT_BYTES}). A test takes it before and after; never reset, so two
|
|
71
|
+
* readers of it cannot disturb each other.
|
|
72
|
+
*/
|
|
73
|
+
export declare function engineWork(): {
|
|
74
|
+
applyPatch: number;
|
|
75
|
+
cloneBytes: number;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Applies one patch to a module's content. Nothing is mutated: the module
|
|
79
|
+
* passed in is still what it was, and the one returned shares every subtree
|
|
80
|
+
* the patch did not touch.
|
|
81
|
+
*
|
|
82
|
+
* `failed` means the patch does not apply, and then none of it does: half a
|
|
83
|
+
* patch is half an edit. The caller stops the module's chain there.
|
|
84
|
+
*
|
|
85
|
+
* `schema` is the module's serialized schema: it says which ops reach into a
|
|
86
|
+
* `.jsonValues()` entry. Without one, every op is an op on the source.
|
|
87
|
+
*/
|
|
88
|
+
export declare function applyPatchRecord(module: ModuleContent, record: PatchRecord, schema: SerializedSchema | undefined): PatchRecordResult;
|
|
89
|
+
/**
|
|
90
|
+
* A module's chain: every patch in order, stopping at the first that does
|
|
91
|
+
* not apply. `applied` is how many did, so the chain the content is AT is
|
|
92
|
+
* the first `applied` ids -- what its version names.
|
|
93
|
+
*/
|
|
94
|
+
export declare function applyChain(module: ModuleContent, records: readonly PatchRecord[], schema: SerializedSchema | undefined): {
|
|
95
|
+
module: ModuleContent;
|
|
96
|
+
applied: number;
|
|
97
|
+
failed: {
|
|
98
|
+
patchId: PatchId;
|
|
99
|
+
message: string;
|
|
100
|
+
} | null;
|
|
101
|
+
};
|
|
102
|
+
/**
|
|
103
|
+
* What an entry is in this module: what the chain made it, or -- for one the
|
|
104
|
+
* chain never touched -- the base's, if the source has its marker.
|
|
105
|
+
*/
|
|
106
|
+
export declare function entryState(module: ModuleContent, key: string): EntryState | undefined;
|
|
107
|
+
/** An entry's content, read. */
|
|
108
|
+
export type EntryContent = {
|
|
109
|
+
kind: "content";
|
|
110
|
+
content: ReadonlyJSONValue;
|
|
111
|
+
} | {
|
|
112
|
+
kind: "missing";
|
|
113
|
+
} | {
|
|
114
|
+
kind: "error";
|
|
115
|
+
message: string;
|
|
116
|
+
patchId?: PatchId;
|
|
117
|
+
};
|
|
118
|
+
/**
|
|
119
|
+
* An entry's content: what {@link entryState} says, with what it was waiting
|
|
120
|
+
* for applied. `baseContentOf` is the BASE content of an entry by key (its
|
|
121
|
+
* `*.val.json`, or what a save left there), `undefined` for one the base does
|
|
122
|
+
* not have.
|
|
123
|
+
*/
|
|
124
|
+
export declare function resolveEntry(key: string, state: EntryState | undefined, baseContentOf: (key: string) => ReadonlyJSONValue | undefined): EntryContent;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a build's `val.modules` registry can be imported from: phase 9 of
|
|
3
|
+
* `docs/plans/one-content-model.md`, "Reloading the schema while the Studio
|
|
4
|
+
* runs".
|
|
5
|
+
*
|
|
6
|
+
* A build made by `@valbuild/tanstack-build` (every build the platform
|
|
7
|
+
* serves) emits its `val.modules` as an ES module of its own beside its
|
|
8
|
+
* client chunks, named after the build: `/_app/val-modules-<build>.js`,
|
|
9
|
+
* whose default export is the registry. The server running that build knows
|
|
10
|
+
* its name (`VAL_BUILD`, Val's `publishBuild`) and names the entry on
|
|
11
|
+
* `GET /manifest` (`registry`), so a Studio opened on an older build imports
|
|
12
|
+
* the new one's code and reloads it in place (`HostStore.reloadCode`)
|
|
13
|
+
* instead of being told to reload the page.
|
|
14
|
+
*
|
|
15
|
+
* The convention is the builder's and this file's together: the builder
|
|
16
|
+
* writes the file (`valModulesEntryFile` in `@valbuild/tanstack-build`, which
|
|
17
|
+
* does not depend on this package), and each side pins the same literal,
|
|
18
|
+
* `val-modules-<build>.js` (`buildRegistry.test.ts` here, `valModules.test.ts`
|
|
19
|
+
* there).
|
|
20
|
+
*/
|
|
21
|
+
/** The file a build's registry is emitted as, among its client chunks. */
|
|
22
|
+
export declare function valModulesEntryFile(build: string): string;
|
|
23
|
+
/** Where a build served by the platform serves its registry. */
|
|
24
|
+
export declare function valModulesEntryPath(build: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* What `GET /manifest` says about the code of the build that answered:
|
|
27
|
+
* where its registry is, and which Val it runs. A Studio running another
|
|
28
|
+
* Val cannot take the build's code in (its own stores would run against a
|
|
29
|
+
* new core), so it reloads itself instead (`other-val-version`).
|
|
30
|
+
*/
|
|
31
|
+
export type BuildRegistry = {
|
|
32
|
+
/** The registry's entry, from the site's origin. */
|
|
33
|
+
entry: string;
|
|
34
|
+
/** `@valbuild/core`'s version, as the build runs it. */
|
|
35
|
+
core: string;
|
|
36
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type SerializedSchema } from "@valbuild/core";
|
|
2
|
+
import { result } from "@valbuild/core/fp";
|
|
3
|
+
import { PatchError, type JSONValue, type Operation } from "@valbuild/core/patch";
|
|
4
|
+
/**
|
|
5
|
+
* Classification of a single patch op against a module's serialized schema,
|
|
6
|
+
* used by the commit flow to route ops for `.jsonValues()` records:
|
|
7
|
+
*
|
|
8
|
+
* - `normal`: the op does not descend into a `.jsonValues()` entry; apply it to
|
|
9
|
+
* the `.val.ts` as usual.
|
|
10
|
+
* - `entry`: the op targets a `.jsonValues()` entry. `recordPath` is the path to
|
|
11
|
+
* the record within the module source (empty for a root record/router),
|
|
12
|
+
* `entryKey` is the entry key, and `subPath` is the remaining path inside the
|
|
13
|
+
* entry (empty when the op targets the entry value itself, e.g. add/remove of
|
|
14
|
+
* the whole entry).
|
|
15
|
+
*/
|
|
16
|
+
export type JsonValuesOpClass = {
|
|
17
|
+
kind: "normal";
|
|
18
|
+
} | {
|
|
19
|
+
kind: "entry";
|
|
20
|
+
recordPath: string[];
|
|
21
|
+
entryKey: string;
|
|
22
|
+
subPath: string[];
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Walks the serialized schema following the op path. When a `.jsonValues()`
|
|
26
|
+
* record is encountered, the next path segment is the entry key and everything
|
|
27
|
+
* after it lives inside the entry's `*.val.json` (so it does not touch the
|
|
28
|
+
* `.val.ts`). Returns `{ kind: "normal" }` when the op never enters a
|
|
29
|
+
* `.jsonValues()` record.
|
|
30
|
+
*/
|
|
31
|
+
export declare function classifyJsonValuesOp(schema: SerializedSchema, opPath: string[]): JsonValuesOpClass;
|
|
32
|
+
/**
|
|
33
|
+
* Is this op a write of the WHOLE record at the root of a `.jsonValues()`
|
|
34
|
+
* module?
|
|
35
|
+
*
|
|
36
|
+
* The one op {@link classifyJsonValuesOp} cannot classify: it finds the entry
|
|
37
|
+
* key by walking the op path, and the root path has no segments to walk, so a
|
|
38
|
+
* root write reads as `normal` and gets applied to the `.val.ts` - writing
|
|
39
|
+
* markers over the `c.json(() => import(...))` calls that make the entries load
|
|
40
|
+
* at all. {@link expandJsonValuesRootOp} turns it into ops that DO name a key,
|
|
41
|
+
* which everything downstream already handles.
|
|
42
|
+
*
|
|
43
|
+
* `add` counts as well as `replace`: at the root both mean "the document is now
|
|
44
|
+
* this" (see `JSONOps`), so both have to be expanded or the unexpanded one is
|
|
45
|
+
* the same bug again.
|
|
46
|
+
*
|
|
47
|
+
* The value has to BE a record, and that is part of the question rather than a
|
|
48
|
+
* check inside the expansion: a `.jsonValues()` record can be nullable, and
|
|
49
|
+
* `null` at the root means the module no longer has a record at all - there are
|
|
50
|
+
* no entries to write, and it is an ordinary `.val.ts` write, exactly as it was
|
|
51
|
+
* before any of this existed. Same for any other non-record value: not a
|
|
52
|
+
* whole-record write, so not this conversion's to route.
|
|
53
|
+
*/
|
|
54
|
+
export declare function isJsonValuesRootOp(schema: SerializedSchema, op: Operation): boolean;
|
|
55
|
+
/**
|
|
56
|
+
* One entry as it stands right now, for {@link expandJsonValuesRootOp} to
|
|
57
|
+
* expand against.
|
|
58
|
+
*
|
|
59
|
+
* The key set decides add-vs-replace-vs-remove. The content, where the caller
|
|
60
|
+
* has it, decides whether an op is emitted at all: an entry whose content is
|
|
61
|
+
* already what the root write says is left alone rather than rewritten, so
|
|
62
|
+
* putting a module back does not touch every `*.val.json` it did not change.
|
|
63
|
+
* `undefined` means "this entry exists, content unknown" - which is what the
|
|
64
|
+
* Studio's draft source has, because the source holds markers - and yields a
|
|
65
|
+
* `replace`, the safe answer.
|
|
66
|
+
*/
|
|
67
|
+
export type CurrentJsonEntries = ReadonlyMap<string, JSONValue | undefined>;
|
|
68
|
+
/**
|
|
69
|
+
* Fans a whole-record write at a `.jsonValues()` module's root out into ops
|
|
70
|
+
* that each name an entry key.
|
|
71
|
+
*
|
|
72
|
+
* This is THE conversion, and it has exactly one implementation on purpose: the
|
|
73
|
+
* commit flow (`ValOps.prepare`) and the engine that builds draft content
|
|
74
|
+
* (`applyPatchRecord`) both expand through here, so a draft
|
|
75
|
+
* cannot show something other than what publishing writes. A second
|
|
76
|
+
* implementation of the same rule would differ silently, which is the whole
|
|
77
|
+
* failure mode this exists to prevent.
|
|
78
|
+
*
|
|
79
|
+
* The value must be the entries' CONTENT. A module's Source is markers, not
|
|
80
|
+
* content, so a Source handed over here is refused rather than written: that is
|
|
81
|
+
* the shape of the original bug (a revert replaying the archived Source), and
|
|
82
|
+
* it is not recoverable afterwards - the markers replace the entries' content
|
|
83
|
+
* on disk.
|
|
84
|
+
*/
|
|
85
|
+
export declare function expandJsonValuesRootOp(op: Operation, currentEntries: CurrentJsonEntries): result.Result<Operation[], PatchError>;
|
|
86
|
+
/**
|
|
87
|
+
* What a whole-record write says about ONE entry: the same rule
|
|
88
|
+
* {@link expandJsonValuesRootOp} applies to all of them.
|
|
89
|
+
*
|
|
90
|
+
* For a reader that is about one entry. Expanding the whole record to find the
|
|
91
|
+
* one op that names its key costs an op per entry per entry read - the record
|
|
92
|
+
* squared, on exactly the large records this feature is for. The rule itself is
|
|
93
|
+
* `writeForEntry`, shared with the expansion above, so the two cannot come to
|
|
94
|
+
* disagree; only the loop around it differs.
|
|
95
|
+
*
|
|
96
|
+
* `null` means the write says nothing about this entry: it holds that content
|
|
97
|
+
* already, or the record does not name it and it does not exist.
|
|
98
|
+
*/
|
|
99
|
+
export declare function expandJsonValuesRootOpForKey(op: Operation, entryKey: string, currentEntries: CurrentJsonEntries): result.Result<Operation | null, PatchError>;
|
|
100
|
+
/**
|
|
101
|
+
* Rebases a patch op that targets a `.jsonValues()` entry's content so its paths
|
|
102
|
+
* are relative to the entry's `*.val.json` root (drops the record + entry-key
|
|
103
|
+
* prefix). Used to replay the op against the backing JSON file.
|
|
104
|
+
*/
|
|
105
|
+
export declare function rebaseContentOp(op: Operation, prefixLen: number): result.Result<Operation, PatchError>;
|
|
106
|
+
/**
|
|
107
|
+
* The entry a path names when it names a WHOLE entry of the root
|
|
108
|
+
* `.jsonValues()` record, and `undefined` for anything else.
|
|
109
|
+
*/
|
|
110
|
+
export declare function wholeEntryKey(serializedSchema: SerializedSchema | undefined, path: string[]): string | undefined;
|
|
@@ -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>;
|