@valbuild/react 0.132.1 → 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.
@@ -1,5 +1,5 @@
1
1
  import { b as _unsupportedIterableToArray, a as _typeof, _ as _slicedToArray } from './slicedToArray-0c0a3b4a.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 _iterator = _createForOfIteratorHelper(schema.items),
425
- _step;
533
+ var _iterator2 = _createForOfIteratorHelper(schema.items),
534
+ _step2;
426
535
  try {
427
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
428
- var item = _step.value;
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
- _iterator.e(err);
541
+ _iterator2.e(err);
433
542
  } finally {
434
- _iterator.f();
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
- return Array.from(modules);
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) {
@@ -1,5 +1,5 @@
1
1
  import { b as _unsupportedIterableToArray, a as _typeof, _ as _slicedToArray } from './slicedToArray-9cce0d9f.browser.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 _iterator = _createForOfIteratorHelper(schema.items),
425
- _step;
533
+ var _iterator2 = _createForOfIteratorHelper(schema.items),
534
+ _step2;
426
535
  try {
427
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
428
- var item = _step.value;
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
- _iterator.e(err);
541
+ _iterator2.e(err);
433
542
  } finally {
434
- _iterator.f();
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
- return Array.from(modules);
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 _iterator = _createForOfIteratorHelper(schema.items),
427
- _step;
535
+ var _iterator2 = _createForOfIteratorHelper(schema.items),
536
+ _step2;
428
537
  try {
429
- for (_iterator.s(); !(_step = _iterator.n()).done;) {
430
- var item = _step.value;
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
- _iterator.e(err);
543
+ _iterator2.e(err);
435
544
  } finally {
436
- _iterator.f();
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
- return Array.from(modules);
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) {
@@ -1,5 +1,5 @@
1
1
  import { c as _arrayLikeToArray, b as _unsupportedIterableToArray } from '../../dist/slicedToArray-9cce0d9f.browser.esm.js';
2
- import { d as _objectSpread2, r as raw, c as attrs } from '../../dist/raw-57f19ada.browser.esm.js';
2
+ import { d as _objectSpread2, r as raw, c as attrs } from '../../dist/raw-d6f65f06.browser.esm.js';
3
3
  import React from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
  import '@valbuild/core';