@valbuild/react 0.133.0 → 0.134.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +79 -0
- package/dist/declarations/src/stega/index.d.ts +1 -1
- package/dist/declarations/src/stega/stegaEncode.d.ts +47 -1
- package/dist/{raw-eede220f.worker.esm.js → raw-1280c322.worker.esm.js} +165 -8
- package/dist/{raw-fe78b046.cjs.prod.js → raw-3ae44ab5.cjs.prod.js} +164 -7
- package/dist/{raw-7ce61255.esm.js → raw-bd3105cd.esm.js} +165 -8
- package/dist/{raw-57f19ada.browser.esm.js → raw-d6f65f06.browser.esm.js} +165 -8
- package/dist/{raw-512e7a0a.cjs.dev.js → raw-ec62616c.cjs.dev.js} +164 -7
- package/internal/dist/valbuild-react-internal.browser.esm.js +1 -1
- package/internal/dist/valbuild-react-internal.cjs.dev.js +1 -1
- package/internal/dist/valbuild-react-internal.cjs.prod.js +1 -1
- package/internal/dist/valbuild-react-internal.esm.js +1 -1
- package/internal/dist/valbuild-react-internal.worker.esm.js +1 -1
- package/package.json +4 -4
- package/stega/dist/valbuild-react-stega.browser.esm.js +2 -2
- package/stega/dist/valbuild-react-stega.cjs.dev.js +1 -1
- package/stega/dist/valbuild-react-stega.cjs.prod.js +1 -1
- package/stega/dist/valbuild-react-stega.esm.js +2 -2
- package/stega/dist/valbuild-react-stega.worker.esm.js +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,84 @@
|
|
|
1
1
|
# @valbuild/react
|
|
2
2
|
|
|
3
|
+
## 0.134.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#690](https://github.com/valbuild/val/pull/690) [`16c49ea`](https://github.com/valbuild/val/commit/16c49ea6dd97c7a96bbfdf211af9bab5884579e2) Thanks [@freekh](https://github.com/freekh)! - Read a view with `useVal` / `fetchVal`.
|
|
8
|
+
|
|
9
|
+
A `s.view()` field reads as a pointer with no properties on it. It can now be handed to a reader, which resolves it to the module it names:
|
|
10
|
+
|
|
11
|
+
```tsx
|
|
12
|
+
const page = useVal(pageVal);
|
|
13
|
+
const header = useVal(page.header); // the header module's content, typed
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
The point is single source of truth rather than a new capability: the page declares which module it shows, and a component follows that declaration instead of importing the target a second time. Change `s.view(headerVal)` to `s.view(navVal)` and the reader follows; an import would have kept reading the header.
|
|
17
|
+
|
|
18
|
+
The readers that take a MODULE rather than a value follow the same rule — `useValKey`, `useValRoute`, `useValRouteUrl` and the `fetchVal*` counterparts, in both the Next and TanStack packages:
|
|
19
|
+
|
|
20
|
+
```tsx
|
|
21
|
+
const page = useVal(pageVal);
|
|
22
|
+
const note = useValRoute(page.notes, params); // the router module the view names
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Works in draft mode, including when the page itself has a pending edit. Two things to know:
|
|
26
|
+
|
|
27
|
+
- **Resolve a handle in the component that read the module containing it.** A handle carries the module it points at, and that cannot survive serialization — so one passed from a server component to a client component as a prop arrives empty. It throws with an explanation rather than handing back the pointer. This is why it throws rather than returning nothing: every one of the route and key readers already uses `null` / `undefined` to mean "no such entry", so a quiet answer would be indistinguishable from a 404.
|
|
28
|
+
- **`ValView<Source>` is now a member of `SelectorSource`**, since a reader accepts one.
|
|
29
|
+
|
|
30
|
+
- [#680](https://github.com/valbuild/val/pull/680) [`eaa265e`](https://github.com/valbuild/val/commit/eaa265e78d9f6d2a1b685c38901612b8a3704eed) Thanks [@freekh](https://github.com/freekh)! - Add `s.view()`: point at another module, so editors can reach it from the page it belongs to.
|
|
31
|
+
|
|
32
|
+
Content that has to live in its own module — a `keyOf` target, shared settings, a route-keyed record — is invisible from the page an editor thinks of it as part of. A view field puts a row on that page's screen that leads to it:
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import employeesVal from "../data/employees.val";
|
|
36
|
+
|
|
37
|
+
const schema = s.object({
|
|
38
|
+
title: s.string(),
|
|
39
|
+
employees: s.view(employeesVal),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
export default c.define("/app/menneskene/page.val.ts", schema, {
|
|
43
|
+
title: "Våre folk",
|
|
44
|
+
employees: { view: "/data/employees.val.ts" },
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The source is a pointer and nothing else. The module it names keeps its own source, patches, validation and address, and an editor who follows the row lands on that module's own screen — so it is clear they are changing something other pages use too.
|
|
49
|
+
|
|
50
|
+
A few things worth knowing:
|
|
51
|
+
|
|
52
|
+
- **The path autocompletes, and a wrong one does not compile.** A module now carries its own id in its type, so the source type of the field above is the literal `{ view: "/data/employees.val.ts" }`.
|
|
53
|
+
- **It is not readable in code.** `useVal(pageVal).employees` is a `ValView<…>` — an opaque pointer with no fields on it. Read the module it names directly, as before.
|
|
54
|
+
- **`view` is now a reserved object key**, like `_type` and `patch_id`: `s.object({ view: ... })` no longer compiles. A single `view: string` key is what a view pointer looks like, and an ordinary object with that shape would be indistinguishable from one.
|
|
55
|
+
- **Views may not form a cycle.** `A → B → A`, and a module viewing itself, are reported as module errors by `val validate` and in the Studio.
|
|
56
|
+
- **A pointer that disagrees with its schema is repaired automatically.** It can only happen in hand-written JSON, and the schema is the authority, so `val validate --fix` and saving in the Studio both write the module the schema names.
|
|
57
|
+
- **A view's `hidden` and `readonly` are its own, never the module's it points at.** A view whose target is hidden is still shown, and still leads there.
|
|
58
|
+
|
|
59
|
+
That last one comes with a change to what `hidden()` means on a **module's own schema**, which is the other half of making a shared module usable:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
// Not in the nav, and on exactly one page.
|
|
63
|
+
export default c.define("/data/employees.val.ts", s.record(...).hidden(), { ... });
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
It now means the nav does not list the module — the Explorer for an ordinary module, Media for a gallery — and nothing more. Previously it also blanked the module's own page, so a module you had hidden was still in the nav and showed nothing when opened. A hidden module is now reached from an `s.view()` row, from search or from a validation error, and renders in full when you get there.
|
|
67
|
+
|
|
68
|
+
One gap worth naming rather than leaving to be discovered: a view pointing at a
|
|
69
|
+
module the project does not have is reported by the FIELD — the row says the
|
|
70
|
+
target is missing — but not by `val validate`. The schema check compares the
|
|
71
|
+
pointer against the schema, and the cycle check deliberately skips a target that
|
|
72
|
+
is not a module of the project, so neither catches it. A project-level check
|
|
73
|
+
belongs with them and is not here yet.
|
|
74
|
+
|
|
75
|
+
### Patch Changes
|
|
76
|
+
|
|
77
|
+
- Updated dependencies [[`1c31dcc`](https://github.com/valbuild/val/commit/1c31dcca7f1bb0199350567fd579de29f48bb26d), [`688b9e3`](https://github.com/valbuild/val/commit/688b9e36b821323cda6870cdff03dd36ec3e782f), [`16c49ea`](https://github.com/valbuild/val/commit/16c49ea6dd97c7a96bbfdf211af9bab5884579e2), [`eaa265e`](https://github.com/valbuild/val/commit/eaa265e78d9f6d2a1b685c38901612b8a3704eed)]:
|
|
78
|
+
- @valbuild/ui@0.134.0
|
|
79
|
+
- @valbuild/core@0.134.0
|
|
80
|
+
- @valbuild/shared@0.134.0
|
|
81
|
+
|
|
3
82
|
## 0.133.0
|
|
4
83
|
|
|
5
84
|
### Patch Changes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { autoTagJSX } from "./autoTagJSX.js";
|
|
2
|
-
export { stegaEncode, getModuleIds, stegaClean, type ValEncodedString, type StegaOfSource, type StegaOfRichTextSource, type File, type Image, type RichText, } from "./stegaEncode.js";
|
|
2
|
+
export { stegaEncode, getModuleIds, stegaClean, type ValEncodedString, type StegaOfSource, type ResolvedVal, type Resolvable, type JsonEntryContentOf, type RouteValueOf, type StegaOfRichTextSource, type File, type Image, type RichText, } from "./stegaEncode.js";
|
|
3
3
|
export { stegaDecodeStrings } from "./stegaDecodeStrings.js";
|
|
4
4
|
export { attrs } from "./attrs.js";
|
|
5
5
|
export { raw, type DecodeVal } from "./raw.js";
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Json, RichTextSource, MediaHotspot, RichTextOptions, ImageSource } from "@valbuild/core";
|
|
2
2
|
import { FileSource, Source, SourceObject } from "@valbuild/core";
|
|
3
|
+
import type { ValView, ValViewSource } from "@valbuild/core";
|
|
3
4
|
import { JsonPrimitive } from "@valbuild/core";
|
|
4
5
|
import { SourceArray } from "@valbuild/core";
|
|
5
6
|
import { RawString } from "@valbuild/core";
|
|
7
|
+
import type { GenericSelector, JsonSource, SelectorOf, SelectorSource } from "@valbuild/core";
|
|
6
8
|
declare const brand: unique symbol;
|
|
7
9
|
/**
|
|
8
10
|
* ValEncodedString is a string that is encoded using steganography.
|
|
@@ -175,9 +177,53 @@ export type StegaOfRichTextSource<T extends Source> = Json extends T ? Json : T
|
|
|
175
177
|
export type RichText<O extends RichTextOptions> = StegaOfRichTextSource<RichTextSource<O>> & {
|
|
176
178
|
readonly __brand?: "RichText";
|
|
177
179
|
};
|
|
178
|
-
export type StegaOfSource<T extends Source> = Json extends T ? Json : T extends RichTextSource<infer O> ? RichText<O> : T extends ImageSource ? Image : T extends FileSource ? File : T extends SourceObject ? {
|
|
180
|
+
export type StegaOfSource<T extends Source> = Json extends T ? Json : T extends RichTextSource<infer O> ? RichText<O> : T extends ImageSource ? Image : T extends FileSource ? File : T extends ValViewSource<string, infer Target> ? ValView<Target> : T extends SourceObject ? {
|
|
179
181
|
[key in keyof T]: StegaOfSource<T[key]>;
|
|
180
182
|
} : T extends SourceArray ? StegaOfSource<T[number]>[] : T extends RawString ? string : string extends T ? ValEncodedString : T extends JsonPrimitive ? T : never;
|
|
183
|
+
/**
|
|
184
|
+
* What resolving `T` gives back — the one definition the framework readers
|
|
185
|
+
* share.
|
|
186
|
+
*
|
|
187
|
+
* Two shapes go in. A module or selector resolves as it always has. A
|
|
188
|
+
* {@link ValView}, which is what a `s.view()` field reads as, resolves to the
|
|
189
|
+
* module it points at: the page declares what it shows, and a reader follows
|
|
190
|
+
* that declaration instead of importing the target a second time.
|
|
191
|
+
*
|
|
192
|
+
* One definition rather than one per reader — `useVal`, `fetchVal`,
|
|
193
|
+
* `initValContent` and the TanStack client each had their own copy of the
|
|
194
|
+
* selector half, which is four places for the view half to be forgotten in.
|
|
195
|
+
*
|
|
196
|
+
* `Target extends Source` is checked HERE rather than on `ValView` itself:
|
|
197
|
+
* `ValView` is built from `ValViewSource`, which is a member of the `Source`
|
|
198
|
+
* union, so a constraint there is a circular type reference.
|
|
199
|
+
*
|
|
200
|
+
* The outer arms are wrapped in tuples so the conditional does not DISTRIBUTE
|
|
201
|
+
* over a union: distributing it re-entered `StegaOfSource` per member and the
|
|
202
|
+
* async readers hit "Type instantiation is excessively deep and possibly
|
|
203
|
+
* infinite" — `useVal` did not, because a `Promise<...>` around it is one more
|
|
204
|
+
* level than the checker had left.
|
|
205
|
+
*/
|
|
206
|
+
export type ResolvedVal<T extends SelectorSource> = [T] extends [
|
|
207
|
+
ValView<infer Target>
|
|
208
|
+
] ? [Target] extends [Source] ? StegaOfSource<Target> : never : SelectorOf<T> extends GenericSelector<infer S> ? StegaOfSource<S> : never;
|
|
209
|
+
/** What a reader accepts. A view handle is a `SelectorSource`, so this is it. */
|
|
210
|
+
export type Resolvable = SelectorSource;
|
|
211
|
+
/**
|
|
212
|
+
* The source of whichever arm of `ResolvableModule` a reader was given — the
|
|
213
|
+
* module's own, or that of the module a view points at.
|
|
214
|
+
*/
|
|
215
|
+
type SourceOfResolvable<T> = [T] extends [ValView<infer Target>] ? Target : T extends GenericSelector<infer S> ? S : never;
|
|
216
|
+
/**
|
|
217
|
+
* The (loosened) content type a single `.jsonValues()` entry resolves to.
|
|
218
|
+
*
|
|
219
|
+
* Here rather than in the framework packages because there were four identical
|
|
220
|
+
* copies of it — next's client and rsc readers, tanstack's client and server —
|
|
221
|
+
* and the view arm would have had to be added to each. Same reason
|
|
222
|
+
* {@link ResolvedVal} lives here.
|
|
223
|
+
*/
|
|
224
|
+
export type JsonEntryContentOf<T> = SourceOfResolvable<T> extends Record<string, infer V> ? V extends JsonSource<infer C> ? C : never : never;
|
|
225
|
+
/** What a route reader gives back for the entry the params matched. */
|
|
226
|
+
export type RouteValueOf<T> = SourceOfResolvable<T> extends SourceObject ? NonNullable<SourceOfResolvable<T>>[string] extends JsonSource<infer C> ? C | null : StegaOfSource<NonNullable<SourceOfResolvable<T>>[string]> | null : never;
|
|
181
227
|
export declare function stegaEncode(input: any, opts: {
|
|
182
228
|
getModule?: (modulePath: string) => any;
|
|
183
229
|
disabled?: boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { b as _unsupportedIterableToArray, a as _typeof, _ as _slicedToArray } from './slicedToArray-7d0170b6.worker.esm.js';
|
|
2
|
-
import { Internal } from '@valbuild/core';
|
|
2
|
+
import { Internal, isValViewSource } from '@valbuild/core';
|
|
3
3
|
import { vercelStegaDecodeAll, vercelStegaSplit, vercelStegaCombine } from '@vercel/stega';
|
|
4
4
|
|
|
5
5
|
function _createForOfIteratorHelper(r, e) {
|
|
@@ -142,6 +142,48 @@ function _objectSpread2(e) {
|
|
|
142
142
|
* RichText is accessible by users (after conversion via useVal / fetchVal)
|
|
143
143
|
**/
|
|
144
144
|
|
|
145
|
+
/**
|
|
146
|
+
* What resolving `T` gives back — the one definition the framework readers
|
|
147
|
+
* share.
|
|
148
|
+
*
|
|
149
|
+
* Two shapes go in. A module or selector resolves as it always has. A
|
|
150
|
+
* {@link ValView}, which is what a `s.view()` field reads as, resolves to the
|
|
151
|
+
* module it points at: the page declares what it shows, and a reader follows
|
|
152
|
+
* that declaration instead of importing the target a second time.
|
|
153
|
+
*
|
|
154
|
+
* One definition rather than one per reader — `useVal`, `fetchVal`,
|
|
155
|
+
* `initValContent` and the TanStack client each had their own copy of the
|
|
156
|
+
* selector half, which is four places for the view half to be forgotten in.
|
|
157
|
+
*
|
|
158
|
+
* `Target extends Source` is checked HERE rather than on `ValView` itself:
|
|
159
|
+
* `ValView` is built from `ValViewSource`, which is a member of the `Source`
|
|
160
|
+
* union, so a constraint there is a circular type reference.
|
|
161
|
+
*
|
|
162
|
+
* The outer arms are wrapped in tuples so the conditional does not DISTRIBUTE
|
|
163
|
+
* over a union: distributing it re-entered `StegaOfSource` per member and the
|
|
164
|
+
* async readers hit "Type instantiation is excessively deep and possibly
|
|
165
|
+
* infinite" — `useVal` did not, because a `Promise<...>` around it is one more
|
|
166
|
+
* level than the checker had left.
|
|
167
|
+
*/
|
|
168
|
+
|
|
169
|
+
/** What a reader accepts. A view handle is a `SelectorSource`, so this is it. */
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* The source of whichever arm of `ResolvableModule` a reader was given — the
|
|
173
|
+
* module's own, or that of the module a view points at.
|
|
174
|
+
*/
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* The (loosened) content type a single `.jsonValues()` entry resolves to.
|
|
178
|
+
*
|
|
179
|
+
* Here rather than in the framework packages because there were four identical
|
|
180
|
+
* copies of it — next's client and rsc readers, tanstack's client and server —
|
|
181
|
+
* and the view arm would have had to be added to each. Same reason
|
|
182
|
+
* {@link ResolvedVal} lives here.
|
|
183
|
+
*/
|
|
184
|
+
|
|
185
|
+
/** What a route reader gives back for the entry the params matched. */
|
|
186
|
+
|
|
145
187
|
/**
|
|
146
188
|
* Resolves the matching variant of a discriminated union from the value's tag.
|
|
147
189
|
* Returns the matching schema or null if no match is found.
|
|
@@ -241,7 +283,55 @@ function handleRichTextSchema(sourceOrSelector, recOpts, rec) {
|
|
|
241
283
|
return sourceOrSelector;
|
|
242
284
|
}
|
|
243
285
|
function stegaEncode(input, opts) {
|
|
286
|
+
var viewModules = new Map();
|
|
287
|
+
// Handed a view handle rather than a module: resolve it and encode what it
|
|
288
|
+
// points at. This is what makes `useVal(page.header)` read the header.
|
|
289
|
+
var resolved = Internal.viewHandleModule(input);
|
|
290
|
+
if (resolved !== undefined) {
|
|
291
|
+
return stegaEncode(resolved, opts);
|
|
292
|
+
}
|
|
293
|
+
// A view pointer with no module on it. The module rides on a symbol, and
|
|
294
|
+
// symbols do not survive serialization — so this is a handle that crossed the
|
|
295
|
+
// server/client boundary as a prop, or one read out of raw JSON. Resolving it
|
|
296
|
+
// would hand back the pointer itself, which looks like content and is not, so
|
|
297
|
+
// say what happened instead.
|
|
298
|
+
if (isValViewSource(input)) {
|
|
299
|
+
throw Error("Cannot resolve the view of '".concat(input.view, "': it carries no module. ") + "Either it crossed a server/client boundary, which drops the module because it rides on a symbol, " + "or it points at a different module than its schema declares \u2014 which `val validate --fix` repairs. " + "Resolve it in the same component that read the module containing it, or read '".concat(input.view, "' directly."));
|
|
300
|
+
}
|
|
244
301
|
function rec(sourceOrSelector, recOpts) {
|
|
302
|
+
// A view is a pointer at another module. Weaving an edit tag into it would
|
|
303
|
+
// corrupt the path it holds, and there is nothing of the target here to
|
|
304
|
+
// encode — the target is its own module, encoded when it is read.
|
|
305
|
+
//
|
|
306
|
+
// The module it names rides along on a symbol, so `useVal(page.header)` can
|
|
307
|
+
// resolve it without a path-to-module registry the app does not have. The
|
|
308
|
+
// pointer itself is unchanged: symbols do not serialize, so this is still
|
|
309
|
+
// `{ view: "/foo.val.ts" }` to anything that looks at it as data.
|
|
310
|
+
if (recOpts !== null && recOpts !== void 0 && recOpts.schema && recOpts.schema.type === "view") {
|
|
311
|
+
var valModule = viewModules.get(recOpts.schema.moduleFilePath);
|
|
312
|
+
if (valModule === undefined || !isValViewSource(sourceOrSelector)) {
|
|
313
|
+
return sourceOrSelector;
|
|
314
|
+
}
|
|
315
|
+
/*
|
|
316
|
+
* The POINTER decides what may be attached, not the schema alone.
|
|
317
|
+
*
|
|
318
|
+
* The two can disagree: a `.val.ts` cannot express it (the source type is
|
|
319
|
+
* the literal path), but hand-written JSON and a patch can, which is the
|
|
320
|
+
* whole reason `view:check-module` exists as a fix. Keying only on the
|
|
321
|
+
* schema attached the schema's module to a pointer naming a different one,
|
|
322
|
+
* so `useVal(page.field)` read a module the value does not name — silently,
|
|
323
|
+
* and looking exactly like a correct read.
|
|
324
|
+
*
|
|
325
|
+
* So a mismatch gets no handle: the value stays the bare pointer, and
|
|
326
|
+
* reading it throws rather than answering with the wrong module. The
|
|
327
|
+
* Studio still renders the field and still offers the repair; nothing here
|
|
328
|
+
* refuses to encode the page over it.
|
|
329
|
+
*/
|
|
330
|
+
if (sourceOrSelector.view !== recOpts.schema.moduleFilePath) {
|
|
331
|
+
return sourceOrSelector;
|
|
332
|
+
}
|
|
333
|
+
return Internal.createViewHandle(sourceOrSelector, valModule);
|
|
334
|
+
}
|
|
245
335
|
if (recOpts !== null && recOpts !== void 0 && recOpts.schema && isKeyOfSchema(recOpts === null || recOpts === void 0 ? void 0 : recOpts.schema)) {
|
|
246
336
|
return sourceOrSelector;
|
|
247
337
|
}
|
|
@@ -304,6 +394,25 @@ function stegaEncode(input, opts) {
|
|
|
304
394
|
var selectorPath = Internal.getValPath(sourceOrSelector);
|
|
305
395
|
if (selectorPath) {
|
|
306
396
|
var newSchema = Internal.getSchema(sourceOrSelector);
|
|
397
|
+
// The modules this module's views point at. Collected HERE because this
|
|
398
|
+
// is the only place with the schema INSTANCE — everything below walks
|
|
399
|
+
// the serialized schema, which carries a path and not a module. Merged
|
|
400
|
+
// rather than replaced: a handle resolved by `useVal` re-enters here as
|
|
401
|
+
// its own module, and its parent's views must stay resolvable.
|
|
402
|
+
var _iterator = _createForOfIteratorHelper(Internal.viewModulesOf(newSchema)),
|
|
403
|
+
_step;
|
|
404
|
+
try {
|
|
405
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
406
|
+
var _step$value = _slicedToArray(_step.value, 2),
|
|
407
|
+
path = _step$value[0],
|
|
408
|
+
_valModule = _step$value[1];
|
|
409
|
+
viewModules.set(path, _valModule);
|
|
410
|
+
}
|
|
411
|
+
} catch (err) {
|
|
412
|
+
_iterator.e(err);
|
|
413
|
+
} finally {
|
|
414
|
+
_iterator.f();
|
|
415
|
+
}
|
|
307
416
|
return rec(opts.getModule && opts.getModule(selectorPath) !== undefined ? opts.getModule(selectorPath) : Internal.getSource(sourceOrSelector), {
|
|
308
417
|
path: selectorPath,
|
|
309
418
|
schema: newSchema === null || newSchema === void 0 ? void 0 : newSchema["executeSerialize"]()
|
|
@@ -421,24 +530,65 @@ function collectReferencedModulesFromSchema(schema, acc) {
|
|
|
421
530
|
} else if (schema.type === "array" || schema.type === "record") {
|
|
422
531
|
collectReferencedModulesFromSchema(schema.item, acc);
|
|
423
532
|
} else if (schema.type === "discriminated-union") {
|
|
424
|
-
var
|
|
425
|
-
|
|
533
|
+
var _iterator2 = _createForOfIteratorHelper(schema.items),
|
|
534
|
+
_step2;
|
|
426
535
|
try {
|
|
427
|
-
for (
|
|
428
|
-
var item =
|
|
536
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
537
|
+
var item = _step2.value;
|
|
429
538
|
collectReferencedModulesFromSchema(item, acc);
|
|
430
539
|
}
|
|
431
540
|
} catch (err) {
|
|
432
|
-
|
|
541
|
+
_iterator2.e(err);
|
|
433
542
|
} finally {
|
|
434
|
-
|
|
543
|
+
_iterator2.f();
|
|
435
544
|
}
|
|
436
545
|
}
|
|
437
546
|
}
|
|
438
547
|
function stegaClean(source) {
|
|
439
548
|
return vercelStegaSplit(source).cleaned;
|
|
440
549
|
}
|
|
550
|
+
|
|
551
|
+
/**
|
|
552
|
+
* Answers already computed, keyed by the selector they were computed from.
|
|
553
|
+
*
|
|
554
|
+
* `getModuleIds` is not cheap: it calls `executeSerialize()`, which rebuilds the
|
|
555
|
+
* whole serialized schema tree on every call — ~31us for a 40-field schema with
|
|
556
|
+
* a nested array, against ~4us for a small one. It is called from `useValStega`
|
|
557
|
+
* on every render whose `useMemo` misses.
|
|
558
|
+
*
|
|
559
|
+
* That memo misses on every render for a VIEW, and cannot be fixed there: the
|
|
560
|
+
* handle is built by `createViewHandle` inside `stegaEncode`, so `page.authors`
|
|
561
|
+
* is a fresh object each time the page is encoded, and `[selector]` is a new
|
|
562
|
+
* dependency every render. Caching here rather than in the hooks fixes it for
|
|
563
|
+
* both copies of the hook at once, and for any other caller.
|
|
564
|
+
*
|
|
565
|
+
* Keyed on the selector, which is the module itself for the case that matters —
|
|
566
|
+
* a view resolves to it on the line below, and a module is a module-level
|
|
567
|
+
* constant, so the entry is hit for the life of the process. A fresh nested
|
|
568
|
+
* selector misses, as it did before; a `WeakMap` lets those entries go.
|
|
569
|
+
*
|
|
570
|
+
* The array is shared, so it is frozen: nothing may sort or splice it in place.
|
|
571
|
+
* Every consumer today copies first (`createSubscriberId` does `paths.slice()`),
|
|
572
|
+
* and freezing is what keeps that true.
|
|
573
|
+
*/
|
|
574
|
+
var moduleIdsCache = new WeakMap();
|
|
441
575
|
function getModuleIds(input) {
|
|
576
|
+
// A view handle names one module: the one it points at. Resolved first so a
|
|
577
|
+
// `useVal(page.header)` subscribes to the header rather than to nothing — and
|
|
578
|
+
// so the recursive call lands on the module, which is what the cache above
|
|
579
|
+
// can actually key on.
|
|
580
|
+
var resolved = Internal.viewHandleModule(input);
|
|
581
|
+
if (resolved !== undefined) {
|
|
582
|
+
return getModuleIds(resolved);
|
|
583
|
+
}
|
|
584
|
+
var cacheable = _typeof(input) === "object" && input !== null;
|
|
585
|
+
if (cacheable) {
|
|
586
|
+
var cached = moduleIdsCache.get(input);
|
|
587
|
+
if (cached) {
|
|
588
|
+
// Frozen, so handing the same array to every caller is safe.
|
|
589
|
+
return cached;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
442
592
|
var modules = new Set();
|
|
443
593
|
function rec(sourceOrSelector) {
|
|
444
594
|
if (_typeof(sourceOrSelector) === "object") {
|
|
@@ -482,7 +632,14 @@ function getModuleIds(input) {
|
|
|
482
632
|
return;
|
|
483
633
|
}
|
|
484
634
|
rec(input);
|
|
485
|
-
|
|
635
|
+
var moduleIds = Array.from(modules);
|
|
636
|
+
// Frozen before it is shared, not after: a consumer that sorts in place would
|
|
637
|
+
// otherwise corrupt every later caller's answer, and silently.
|
|
638
|
+
Object.freeze(moduleIds);
|
|
639
|
+
if (cacheable) {
|
|
640
|
+
moduleIdsCache.set(input, moduleIds);
|
|
641
|
+
}
|
|
642
|
+
return moduleIds;
|
|
486
643
|
}
|
|
487
644
|
|
|
488
645
|
function attrs(target) {
|
|
@@ -144,6 +144,48 @@ function _objectSpread2(e) {
|
|
|
144
144
|
* RichText is accessible by users (after conversion via useVal / fetchVal)
|
|
145
145
|
**/
|
|
146
146
|
|
|
147
|
+
/**
|
|
148
|
+
* What resolving `T` gives back — the one definition the framework readers
|
|
149
|
+
* share.
|
|
150
|
+
*
|
|
151
|
+
* Two shapes go in. A module or selector resolves as it always has. A
|
|
152
|
+
* {@link ValView}, which is what a `s.view()` field reads as, resolves to the
|
|
153
|
+
* module it points at: the page declares what it shows, and a reader follows
|
|
154
|
+
* that declaration instead of importing the target a second time.
|
|
155
|
+
*
|
|
156
|
+
* One definition rather than one per reader — `useVal`, `fetchVal`,
|
|
157
|
+
* `initValContent` and the TanStack client each had their own copy of the
|
|
158
|
+
* selector half, which is four places for the view half to be forgotten in.
|
|
159
|
+
*
|
|
160
|
+
* `Target extends Source` is checked HERE rather than on `ValView` itself:
|
|
161
|
+
* `ValView` is built from `ValViewSource`, which is a member of the `Source`
|
|
162
|
+
* union, so a constraint there is a circular type reference.
|
|
163
|
+
*
|
|
164
|
+
* The outer arms are wrapped in tuples so the conditional does not DISTRIBUTE
|
|
165
|
+
* over a union: distributing it re-entered `StegaOfSource` per member and the
|
|
166
|
+
* async readers hit "Type instantiation is excessively deep and possibly
|
|
167
|
+
* infinite" — `useVal` did not, because a `Promise<...>` around it is one more
|
|
168
|
+
* level than the checker had left.
|
|
169
|
+
*/
|
|
170
|
+
|
|
171
|
+
/** What a reader accepts. A view handle is a `SelectorSource`, so this is it. */
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* The source of whichever arm of `ResolvableModule` a reader was given — the
|
|
175
|
+
* module's own, or that of the module a view points at.
|
|
176
|
+
*/
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* The (loosened) content type a single `.jsonValues()` entry resolves to.
|
|
180
|
+
*
|
|
181
|
+
* Here rather than in the framework packages because there were four identical
|
|
182
|
+
* copies of it — next's client and rsc readers, tanstack's client and server —
|
|
183
|
+
* and the view arm would have had to be added to each. Same reason
|
|
184
|
+
* {@link ResolvedVal} lives here.
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
/** What a route reader gives back for the entry the params matched. */
|
|
188
|
+
|
|
147
189
|
/**
|
|
148
190
|
* Resolves the matching variant of a discriminated union from the value's tag.
|
|
149
191
|
* Returns the matching schema or null if no match is found.
|
|
@@ -243,7 +285,55 @@ function handleRichTextSchema(sourceOrSelector, recOpts, rec) {
|
|
|
243
285
|
return sourceOrSelector;
|
|
244
286
|
}
|
|
245
287
|
function stegaEncode(input, opts) {
|
|
288
|
+
var viewModules = new Map();
|
|
289
|
+
// Handed a view handle rather than a module: resolve it and encode what it
|
|
290
|
+
// points at. This is what makes `useVal(page.header)` read the header.
|
|
291
|
+
var resolved = core.Internal.viewHandleModule(input);
|
|
292
|
+
if (resolved !== undefined) {
|
|
293
|
+
return stegaEncode(resolved, opts);
|
|
294
|
+
}
|
|
295
|
+
// A view pointer with no module on it. The module rides on a symbol, and
|
|
296
|
+
// symbols do not survive serialization — so this is a handle that crossed the
|
|
297
|
+
// server/client boundary as a prop, or one read out of raw JSON. Resolving it
|
|
298
|
+
// would hand back the pointer itself, which looks like content and is not, so
|
|
299
|
+
// say what happened instead.
|
|
300
|
+
if (core.isValViewSource(input)) {
|
|
301
|
+
throw Error("Cannot resolve the view of '".concat(input.view, "': it carries no module. ") + "Either it crossed a server/client boundary, which drops the module because it rides on a symbol, " + "or it points at a different module than its schema declares \u2014 which `val validate --fix` repairs. " + "Resolve it in the same component that read the module containing it, or read '".concat(input.view, "' directly."));
|
|
302
|
+
}
|
|
246
303
|
function rec(sourceOrSelector, recOpts) {
|
|
304
|
+
// A view is a pointer at another module. Weaving an edit tag into it would
|
|
305
|
+
// corrupt the path it holds, and there is nothing of the target here to
|
|
306
|
+
// encode — the target is its own module, encoded when it is read.
|
|
307
|
+
//
|
|
308
|
+
// The module it names rides along on a symbol, so `useVal(page.header)` can
|
|
309
|
+
// resolve it without a path-to-module registry the app does not have. The
|
|
310
|
+
// pointer itself is unchanged: symbols do not serialize, so this is still
|
|
311
|
+
// `{ view: "/foo.val.ts" }` to anything that looks at it as data.
|
|
312
|
+
if (recOpts !== null && recOpts !== void 0 && recOpts.schema && recOpts.schema.type === "view") {
|
|
313
|
+
var valModule = viewModules.get(recOpts.schema.moduleFilePath);
|
|
314
|
+
if (valModule === undefined || !core.isValViewSource(sourceOrSelector)) {
|
|
315
|
+
return sourceOrSelector;
|
|
316
|
+
}
|
|
317
|
+
/*
|
|
318
|
+
* The POINTER decides what may be attached, not the schema alone.
|
|
319
|
+
*
|
|
320
|
+
* The two can disagree: a `.val.ts` cannot express it (the source type is
|
|
321
|
+
* the literal path), but hand-written JSON and a patch can, which is the
|
|
322
|
+
* whole reason `view:check-module` exists as a fix. Keying only on the
|
|
323
|
+
* schema attached the schema's module to a pointer naming a different one,
|
|
324
|
+
* so `useVal(page.field)` read a module the value does not name — silently,
|
|
325
|
+
* and looking exactly like a correct read.
|
|
326
|
+
*
|
|
327
|
+
* So a mismatch gets no handle: the value stays the bare pointer, and
|
|
328
|
+
* reading it throws rather than answering with the wrong module. The
|
|
329
|
+
* Studio still renders the field and still offers the repair; nothing here
|
|
330
|
+
* refuses to encode the page over it.
|
|
331
|
+
*/
|
|
332
|
+
if (sourceOrSelector.view !== recOpts.schema.moduleFilePath) {
|
|
333
|
+
return sourceOrSelector;
|
|
334
|
+
}
|
|
335
|
+
return core.Internal.createViewHandle(sourceOrSelector, valModule);
|
|
336
|
+
}
|
|
247
337
|
if (recOpts !== null && recOpts !== void 0 && recOpts.schema && isKeyOfSchema(recOpts === null || recOpts === void 0 ? void 0 : recOpts.schema)) {
|
|
248
338
|
return sourceOrSelector;
|
|
249
339
|
}
|
|
@@ -306,6 +396,25 @@ function stegaEncode(input, opts) {
|
|
|
306
396
|
var selectorPath = core.Internal.getValPath(sourceOrSelector);
|
|
307
397
|
if (selectorPath) {
|
|
308
398
|
var newSchema = core.Internal.getSchema(sourceOrSelector);
|
|
399
|
+
// The modules this module's views point at. Collected HERE because this
|
|
400
|
+
// is the only place with the schema INSTANCE — everything below walks
|
|
401
|
+
// the serialized schema, which carries a path and not a module. Merged
|
|
402
|
+
// rather than replaced: a handle resolved by `useVal` re-enters here as
|
|
403
|
+
// its own module, and its parent's views must stay resolvable.
|
|
404
|
+
var _iterator = _createForOfIteratorHelper(core.Internal.viewModulesOf(newSchema)),
|
|
405
|
+
_step;
|
|
406
|
+
try {
|
|
407
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
408
|
+
var _step$value = slicedToArray._slicedToArray(_step.value, 2),
|
|
409
|
+
path = _step$value[0],
|
|
410
|
+
_valModule = _step$value[1];
|
|
411
|
+
viewModules.set(path, _valModule);
|
|
412
|
+
}
|
|
413
|
+
} catch (err) {
|
|
414
|
+
_iterator.e(err);
|
|
415
|
+
} finally {
|
|
416
|
+
_iterator.f();
|
|
417
|
+
}
|
|
309
418
|
return rec(opts.getModule && opts.getModule(selectorPath) !== undefined ? opts.getModule(selectorPath) : core.Internal.getSource(sourceOrSelector), {
|
|
310
419
|
path: selectorPath,
|
|
311
420
|
schema: newSchema === null || newSchema === void 0 ? void 0 : newSchema["executeSerialize"]()
|
|
@@ -423,24 +532,65 @@ function collectReferencedModulesFromSchema(schema, acc) {
|
|
|
423
532
|
} else if (schema.type === "array" || schema.type === "record") {
|
|
424
533
|
collectReferencedModulesFromSchema(schema.item, acc);
|
|
425
534
|
} else if (schema.type === "discriminated-union") {
|
|
426
|
-
var
|
|
427
|
-
|
|
535
|
+
var _iterator2 = _createForOfIteratorHelper(schema.items),
|
|
536
|
+
_step2;
|
|
428
537
|
try {
|
|
429
|
-
for (
|
|
430
|
-
var item =
|
|
538
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
539
|
+
var item = _step2.value;
|
|
431
540
|
collectReferencedModulesFromSchema(item, acc);
|
|
432
541
|
}
|
|
433
542
|
} catch (err) {
|
|
434
|
-
|
|
543
|
+
_iterator2.e(err);
|
|
435
544
|
} finally {
|
|
436
|
-
|
|
545
|
+
_iterator2.f();
|
|
437
546
|
}
|
|
438
547
|
}
|
|
439
548
|
}
|
|
440
549
|
function stegaClean(source) {
|
|
441
550
|
return stega.vercelStegaSplit(source).cleaned;
|
|
442
551
|
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Answers already computed, keyed by the selector they were computed from.
|
|
555
|
+
*
|
|
556
|
+
* `getModuleIds` is not cheap: it calls `executeSerialize()`, which rebuilds the
|
|
557
|
+
* whole serialized schema tree on every call — ~31us for a 40-field schema with
|
|
558
|
+
* a nested array, against ~4us for a small one. It is called from `useValStega`
|
|
559
|
+
* on every render whose `useMemo` misses.
|
|
560
|
+
*
|
|
561
|
+
* That memo misses on every render for a VIEW, and cannot be fixed there: the
|
|
562
|
+
* handle is built by `createViewHandle` inside `stegaEncode`, so `page.authors`
|
|
563
|
+
* is a fresh object each time the page is encoded, and `[selector]` is a new
|
|
564
|
+
* dependency every render. Caching here rather than in the hooks fixes it for
|
|
565
|
+
* both copies of the hook at once, and for any other caller.
|
|
566
|
+
*
|
|
567
|
+
* Keyed on the selector, which is the module itself for the case that matters —
|
|
568
|
+
* a view resolves to it on the line below, and a module is a module-level
|
|
569
|
+
* constant, so the entry is hit for the life of the process. A fresh nested
|
|
570
|
+
* selector misses, as it did before; a `WeakMap` lets those entries go.
|
|
571
|
+
*
|
|
572
|
+
* The array is shared, so it is frozen: nothing may sort or splice it in place.
|
|
573
|
+
* Every consumer today copies first (`createSubscriberId` does `paths.slice()`),
|
|
574
|
+
* and freezing is what keeps that true.
|
|
575
|
+
*/
|
|
576
|
+
var moduleIdsCache = new WeakMap();
|
|
443
577
|
function getModuleIds(input) {
|
|
578
|
+
// A view handle names one module: the one it points at. Resolved first so a
|
|
579
|
+
// `useVal(page.header)` subscribes to the header rather than to nothing — and
|
|
580
|
+
// so the recursive call lands on the module, which is what the cache above
|
|
581
|
+
// can actually key on.
|
|
582
|
+
var resolved = core.Internal.viewHandleModule(input);
|
|
583
|
+
if (resolved !== undefined) {
|
|
584
|
+
return getModuleIds(resolved);
|
|
585
|
+
}
|
|
586
|
+
var cacheable = slicedToArray._typeof(input) === "object" && input !== null;
|
|
587
|
+
if (cacheable) {
|
|
588
|
+
var cached = moduleIdsCache.get(input);
|
|
589
|
+
if (cached) {
|
|
590
|
+
// Frozen, so handing the same array to every caller is safe.
|
|
591
|
+
return cached;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
444
594
|
var modules = new Set();
|
|
445
595
|
function rec(sourceOrSelector) {
|
|
446
596
|
if (slicedToArray._typeof(sourceOrSelector) === "object") {
|
|
@@ -484,7 +634,14 @@ function getModuleIds(input) {
|
|
|
484
634
|
return;
|
|
485
635
|
}
|
|
486
636
|
rec(input);
|
|
487
|
-
|
|
637
|
+
var moduleIds = Array.from(modules);
|
|
638
|
+
// Frozen before it is shared, not after: a consumer that sorts in place would
|
|
639
|
+
// otherwise corrupt every later caller's answer, and silently.
|
|
640
|
+
Object.freeze(moduleIds);
|
|
641
|
+
if (cacheable) {
|
|
642
|
+
moduleIdsCache.set(input, moduleIds);
|
|
643
|
+
}
|
|
644
|
+
return moduleIds;
|
|
488
645
|
}
|
|
489
646
|
|
|
490
647
|
function attrs(target) {
|