@valbuild/server 0.97.0 → 0.97.2

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.
@@ -1289,6 +1289,11 @@ export default new Proxy({}, {
1289
1289
  value: "export const ValApp = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValApp' in this file`) } } } )"
1290
1290
  };
1291
1291
  }
1292
+ if (modulePath.includes("/ValModulesClient")) {
1293
+ return {
1294
+ value: "export const ValModulesClient = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValModulesClient' in this file`) } } } ); export const useRegisterValModules = () => { throw new Error(`Cannot use 'useRegisterValModules' in this type of file`) };"
1295
+ };
1296
+ }
1292
1297
  return {
1293
1298
  value: moduleLoader.getModule(modulePath)
1294
1299
  };
@@ -1359,6 +1364,11 @@ export default new Proxy({}, {
1359
1364
  value: requestedName
1360
1365
  };
1361
1366
  }
1367
+ if (requestedName.includes("/ValModulesClient")) {
1368
+ return {
1369
+ value: requestedName
1370
+ };
1371
+ }
1362
1372
  const modulePath = moduleLoader.resolveModulePath(baseModuleName, requestedName);
1363
1373
  return {
1364
1374
  value: modulePath
@@ -1493,7 +1503,6 @@ function encodeJwt(payload, sessionKey) {
1493
1503
  }
1494
1504
 
1495
1505
  /* eslint-disable @typescript-eslint/no-unused-vars */
1496
- const textEncoder$2 = new TextEncoder();
1497
1506
  const jsonOps = new patch.JSONOps();
1498
1507
  const tsOps = new TSOps(document => {
1499
1508
  return fp.pipe(analyzeValModule(document), fp.result.map(({
@@ -1525,47 +1534,6 @@ class ValOps {
1525
1534
  this.configSha = null;
1526
1535
  this.modulesErrors = null;
1527
1536
  }
1528
- hash(input) {
1529
- if (typeof input === "object") {
1530
- return this.hashObject(input);
1531
- }
1532
- return core.Internal.getSHA256Hash(textEncoder$2.encode(input));
1533
- }
1534
- hashObject(obj) {
1535
- const collector = [];
1536
- this.collectObjectRecursive(obj, collector);
1537
- return core.Internal.getSHA256Hash(textEncoder$2.encode(collector.join("")));
1538
- }
1539
- collectObjectRecursive(item, collector) {
1540
- if (typeof item === "string") {
1541
- collector.push(`"`, item, `"`);
1542
- return;
1543
- } else if (typeof item === "number") {
1544
- collector.push(item.toString());
1545
- return;
1546
- } else if (typeof item === "object") {
1547
- if (Array.isArray(item)) {
1548
- collector.push("[");
1549
- for (let i = 0; i < item.length; i++) {
1550
- this.collectObjectRecursive(item[i], collector);
1551
- if (i !== item.length - 1) collector.push(",");
1552
- }
1553
- collector.push("]");
1554
- } else {
1555
- collector.push("{");
1556
- const keys = Object.keys(item).sort();
1557
- keys.forEach((key, i) => {
1558
- collector.push(`"${key}":`);
1559
- this.collectObjectRecursive(item[key], collector);
1560
- if (i !== keys.length - 1) collector.push(",");
1561
- });
1562
- collector.push("}");
1563
- }
1564
- return;
1565
- } else {
1566
- console.warn("Unknown type encountered when hashing object", typeof item, item);
1567
- }
1568
- }
1569
1537
 
1570
1538
  // #region stat
1571
1539
  /**
@@ -1581,93 +1549,23 @@ class ValOps {
1581
1549
  // #region initTree
1582
1550
  async initSources() {
1583
1551
  if (this.baseSha === null || this.sourcesSha === null || this.configSha === null || this.schemaSha === null || this.sources === null || this.schemas === null || this.modulesErrors === null) {
1584
- const currentModulesErrors = [];
1585
- const addModuleError = (message, index, path) => {
1586
- currentModulesErrors[index] = {
1587
- message,
1588
- path: path
1589
- };
1552
+ const extracted = await core.extractValModules(this.valModules);
1553
+ this.sources = extracted.sources;
1554
+ this.schemas = extracted.schemas;
1555
+ this.baseSha = extracted.baseSha;
1556
+ this.schemaSha = extracted.schemaSha;
1557
+ this.sourcesSha = extracted.sourcesSha;
1558
+ this.configSha = extracted.configSha;
1559
+ this.modulesErrors = extracted.moduleErrors;
1560
+ return {
1561
+ baseSha: this.baseSha,
1562
+ schemaSha: this.schemaSha,
1563
+ sourcesSha: this.sourcesSha,
1564
+ configSha: this.configSha,
1565
+ sources: extracted.sources,
1566
+ schemas: extracted.schemas,
1567
+ moduleErrors: extracted.moduleErrors
1590
1568
  };
1591
- const currentSources = {};
1592
- const currentSchemas = {};
1593
- const configSha = this.hash(JSON.stringify(this.valModules.config));
1594
- let sourcesSha = "";
1595
- let baseSha = configSha;
1596
- let schemaSha = configSha;
1597
- for (let moduleIdx = 0; moduleIdx < this.valModules.modules.length; moduleIdx++) {
1598
- const module = this.valModules.modules[moduleIdx];
1599
- if (!module.def) {
1600
- addModuleError("val.modules is missing 'def' property", moduleIdx);
1601
- continue;
1602
- }
1603
- if (typeof module.def !== "function") {
1604
- addModuleError("val.modules 'def' property is not a function", moduleIdx);
1605
- continue;
1606
- }
1607
- await module.def().then(value => {
1608
- if (!value) {
1609
- addModuleError(`val.modules 'def' did not return a value`, moduleIdx);
1610
- return;
1611
- }
1612
- if (!value.default) {
1613
- addModuleError(`val.modules 'def' did not return a default export`, moduleIdx);
1614
- return;
1615
- }
1616
- const path = core.Internal.getValPath(value.default);
1617
- if (path === undefined) {
1618
- addModuleError(`path is undefined`, moduleIdx);
1619
- return;
1620
- }
1621
- const schema = core.Internal.getSchema(value.default);
1622
- if (schema === undefined) {
1623
- addModuleError(`schema in path '${path}' is undefined`, moduleIdx, path);
1624
- return;
1625
- }
1626
- if (!(schema instanceof core.Schema)) {
1627
- addModuleError(`schema in path '${path}' is not an instance of Schema`, moduleIdx, path);
1628
- return;
1629
- }
1630
- if (typeof schema["executeSerialize"] !== "function") {
1631
- addModuleError(`schema.serialize in path '${path}' is not a function`, moduleIdx, path);
1632
- return;
1633
- }
1634
- const source = core.Internal.getSource(value.default);
1635
- if (source === undefined) {
1636
- addModuleError(`source in ${path} is undefined`, moduleIdx, path);
1637
- return;
1638
- }
1639
- let serializedSchema;
1640
- try {
1641
- serializedSchema = schema["executeSerialize"]();
1642
- } catch (e) {
1643
- const message = e instanceof Error ? e.message : JSON.stringify(e);
1644
- addModuleError(`Could not serialize module: '${path}'. Error: ${message}`, moduleIdx, path);
1645
- return;
1646
- }
1647
- const pathM = path;
1648
- currentSources[pathM] = source;
1649
- currentSchemas[pathM] = schema;
1650
- // make sure the checks above is enough that this does not fail - even if val modules are not set up correctly
1651
- sourcesSha = this.hash(sourcesSha + JSON.stringify({
1652
- path,
1653
- source
1654
- }));
1655
- baseSha = this.hash(baseSha + JSON.stringify({
1656
- path,
1657
- schema: serializedSchema,
1658
- source,
1659
- modulesErrors: currentModulesErrors
1660
- }));
1661
- schemaSha = this.hash(schemaSha + JSON.stringify(serializedSchema));
1662
- });
1663
- }
1664
- this.sources = currentSources;
1665
- this.schemas = currentSchemas;
1666
- this.baseSha = baseSha;
1667
- this.schemaSha = schemaSha;
1668
- this.sourcesSha = sourcesSha;
1669
- this.configSha = configSha;
1670
- this.modulesErrors = currentModulesErrors;
1671
1569
  }
1672
1570
  return {
1673
1571
  baseSha: this.baseSha,
@@ -1847,79 +1745,6 @@ class ValOps {
1847
1745
 
1848
1746
  // #region validateSources
1849
1747
  async validateSources(schemas, sources, patchesByModule) {
1850
- const checkKeyIsValid = async (key, sourcePath) => {
1851
- var _schemas$moduleFilePa;
1852
- const [moduleFilePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
1853
- const keyOfModuleSource = sources[moduleFilePath];
1854
- const keyOfModuleSchema = (_schemas$moduleFilePa = schemas[moduleFilePath]) === null || _schemas$moduleFilePa === void 0 ? void 0 : _schemas$moduleFilePa["executeSerialize"]();
1855
- if (keyOfModuleSchema && keyOfModuleSchema.type !== "record") {
1856
- return {
1857
- error: true,
1858
- message: `Expected key at ${sourcePath} to be of type 'record'`
1859
- };
1860
- }
1861
- if (keyOfModuleSource && typeof keyOfModuleSource === "object" && key in keyOfModuleSource) {
1862
- return {
1863
- error: false
1864
- };
1865
- }
1866
- if (!keyOfModuleSource || typeof keyOfModuleSource !== "object") {
1867
- return {
1868
- error: true,
1869
- message: `Expected ${sourcePath} to be a truthy object`
1870
- };
1871
- }
1872
- return {
1873
- error: true,
1874
- message: `Key '${key}' does not exist in ${sourcePath}.`
1875
- };
1876
- };
1877
- const checkRouteIsValid = async (route, includePattern, excludePattern) => {
1878
- // Find all router modules (record schemas with router property)
1879
- const routerModules = [];
1880
- for (const [moduleFilePath, schema] of Object.entries(schemas)) {
1881
- const serializedSchema = schema["executeSerialize"]();
1882
- if (serializedSchema.type === "record" && serializedSchema.router) {
1883
- const source = sources[moduleFilePath];
1884
- if (source && typeof source === "object") {
1885
- routerModules.push({
1886
- path: moduleFilePath,
1887
- routes: Object.keys(source)
1888
- });
1889
- }
1890
- }
1891
- }
1892
- if (routerModules.length === 0) {
1893
- return {
1894
- error: true,
1895
- message: `No router modules found. Route validation requires at least one s.record().router() module.`
1896
- };
1897
- }
1898
-
1899
- // Check if route exists in any router module
1900
- const allRoutes = routerModules.flatMap(m => m.routes);
1901
- const routeExists = allRoutes.includes(route);
1902
- if (!routeExists) {
1903
- // Filter routes by include/exclude patterns for suggestions
1904
- const validRoutes = internal.filterRoutesByPatterns(allRoutes, includePattern, excludePattern);
1905
- return {
1906
- error: true,
1907
- message: `Route '${route}' does not exist in any router module. Available routes: ${validRoutes.slice(0, 10).join(", ")}${validRoutes.length > 10 ? "..." : ""}`
1908
- };
1909
- }
1910
-
1911
- // Validate against include/exclude patterns
1912
- const patternValidation = internal.validateRoutePatterns(route, includePattern, excludePattern);
1913
- if (!patternValidation.valid) {
1914
- return {
1915
- error: true,
1916
- message: patternValidation.message
1917
- };
1918
- }
1919
- return {
1920
- error: false
1921
- };
1922
- };
1923
1748
  const errors = {};
1924
1749
  const files = {};
1925
1750
  const remoteFiles = {};
@@ -1927,15 +1752,27 @@ class ValOps {
1927
1752
  // Build a map of gallery directory → [ModuleFilePath, ...] across ALL modules
1928
1753
  // (must include all modules, not just those being validated, since conflicts can come from any module)
1929
1754
  const galleryDirectoryToModules = new Map();
1755
+ // Build a schema/source snapshot so the shared resolver can cross-reference
1756
+ // keyof:check-keys and router:check-route against every module's data.
1757
+ const snapshot = {
1758
+ schemas: {},
1759
+ sources: {}
1760
+ };
1930
1761
  for (const [moduleFilePathS, schema] of entries) {
1762
+ const moduleFilePath = moduleFilePathS;
1931
1763
  const serialized = schema["executeSerialize"]();
1764
+ snapshot.schemas[moduleFilePath] = serialized;
1765
+ const sourceForModule = sources[moduleFilePath];
1766
+ if (sourceForModule !== undefined) {
1767
+ snapshot.sources[moduleFilePath] = sourceForModule;
1768
+ }
1932
1769
  if (serialized.type === "record" && serialized.mediaType && serialized.directory) {
1933
1770
  const dir = serialized.directory;
1934
1771
  const existing = galleryDirectoryToModules.get(dir);
1935
1772
  if (existing) {
1936
- existing.push(moduleFilePathS);
1773
+ existing.push(moduleFilePath);
1937
1774
  } else {
1938
- galleryDirectoryToModules.set(dir, [moduleFilePathS]);
1775
+ galleryDirectoryToModules.set(dir, [moduleFilePath]);
1939
1776
  }
1940
1777
  }
1941
1778
  }
@@ -1993,78 +1830,12 @@ class ValOps {
1993
1830
  }
1994
1831
  } else if ((_validationError$fixe = validationError.fixes) !== null && _validationError$fixe !== void 0 && _validationError$fixe.includes("image:check-remote") || (_validationError$fixe2 = validationError.fixes) !== null && _validationError$fixe2 !== void 0 && _validationError$fixe2.includes("file:check-remote")) {
1995
1832
  remoteFiles[sourcePath] = validationError.value;
1996
- } else if ((_validationError$fixe3 = validationError.fixes) !== null && _validationError$fixe3 !== void 0 && _validationError$fixe3.includes("keyof:check-keys")) {
1997
- const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
1998
- if (!validationError.value) {
1999
- addError({
2000
- message: `Could not find a value for keyOf at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
2001
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2002
- typeError: true
2003
- });
2004
- } else {
2005
- if (typeof validationError.value !== "object") {
2006
- addError({
2007
- message: `Expected keyOf validation error to have a 'value' property of type 'object'. Found: ${typeof validationError.value}. ${TYPE_ERROR_MESSAGE}`,
2008
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2009
- typeError: true
2010
- });
2011
- } else {
2012
- const key = "key" in validationError.value && validationError.value.key;
2013
- const validationErrorSourcePath = "sourcePath" in validationError.value && validationError.value.sourcePath;
2014
- if (typeof key !== "string") {
2015
- addError({
2016
- message: `Expected keyOf validation error 'value' to have property 'key' of type 'string'. Found: ${typeof key}. ${TYPE_ERROR_MESSAGE}`,
2017
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2018
- typeError: true
2019
- });
2020
- } else if (typeof validationErrorSourcePath !== "string") {
2021
- addError({
2022
- message: `Expected keyOf validation error 'value' to have property 'sourcePath' of type 'string'. Found: ${typeof validationErrorSourcePath}. ${TYPE_ERROR_MESSAGE}`,
2023
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2024
- typeError: true
2025
- });
2026
- } else {
2027
- const res = await checkKeyIsValid(key, validationErrorSourcePath);
2028
- if (res.error) {
2029
- addError({
2030
- message: res.message
2031
- });
2032
- }
2033
- }
2034
- }
2035
- }
2036
- } else if ((_validationError$fixe4 = validationError.fixes) !== null && _validationError$fixe4 !== void 0 && _validationError$fixe4.includes("router:check-route")) {
2037
- const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
2038
- if (!validationError.value) {
2039
- addError({
2040
- message: `Could not find a value for route at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
2041
- typeError: true
2042
- });
2043
- } else {
2044
- if (typeof validationError.value !== "object") {
2045
- addError({
2046
- message: `Expected route validation error to have a 'value' property of type 'object'. Found: ${typeof validationError.value}. ${TYPE_ERROR_MESSAGE}`,
2047
- typeError: true
2048
- });
2049
- } else {
2050
- const route = "route" in validationError.value && validationError.value.route;
2051
- const includePattern = "include" in validationError.value && validationError.value.include;
2052
- const excludePattern = "exclude" in validationError.value && validationError.value.exclude;
2053
- if (typeof route !== "string") {
2054
- addError({
2055
- message: `Expected route validation error 'value' to have property 'route' of type 'string'. Found: ${typeof route}. ${TYPE_ERROR_MESSAGE}`,
2056
- typeError: true
2057
- });
2058
- } else {
2059
- const res = await checkRouteIsValid(route, includePattern && typeof includePattern === "object" && "source" in includePattern && "flags" in includePattern ? includePattern : undefined, excludePattern && typeof excludePattern === "object" && "source" in excludePattern && "flags" in excludePattern ? excludePattern : undefined);
2060
- if (res.error) {
2061
- addError({
2062
- message: res.message
2063
- });
2064
- }
2065
- }
2066
- }
1833
+ } else if ((_validationError$fixe3 = validationError.fixes) !== null && _validationError$fixe3 !== void 0 && _validationError$fixe3.includes("keyof:check-keys") || (_validationError$fixe4 = validationError.fixes) !== null && _validationError$fixe4 !== void 0 && _validationError$fixe4.includes("router:check-route")) {
1834
+ const resolved = internal.resolveSchemaSourceFixForError(validationError, snapshot);
1835
+ if (resolved && resolved.status === "remaining") {
1836
+ addError(resolved.error);
2067
1837
  }
1838
+ // resolved.status === "resolved" → drop silently
2068
1839
  } else if ((_validationError$fixe5 = validationError.fixes) !== null && _validationError$fixe5 !== void 0 && _validationError$fixe5.includes("images:check-unique-folder") || (_validationError$fixe6 = validationError.fixes) !== null && _validationError$fixe6 !== void 0 && _validationError$fixe6.includes("files:check-unique-folder")) {
2069
1840
  const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
2070
1841
  if (!validationError.value || typeof validationError.value !== "object") {
@@ -5915,21 +5686,29 @@ const ValServer = (valModules, options, callbacks) => {
5915
5686
  const schemasRes = await serverOps.getSchemas();
5916
5687
  let sourcesRes = await serverOps.getSources();
5917
5688
  const unpatchedSources = sourcesRes.sources;
5918
- const onlyPatchedTreeModules = await serverOps.getSources({
5919
- ...patchAnalysis,
5920
- ...patchOps
5921
- });
5922
- sourcesRes = {
5923
- sources: {
5924
- ...sourcesRes.sources,
5925
- ...(onlyPatchedTreeModules.sources || {})
5926
- },
5927
- errors: {
5928
- ...sourcesRes.errors,
5929
- ...(onlyPatchedTreeModules.errors || {})
5930
- }
5689
+ // Default to true to keep the legacy contract for older clients.
5690
+ // The studio client always passes false: it owns patch application
5691
+ // and rendering, and treats /sources/~ as a pure un-patched read.
5692
+ const applyPatches = query.apply_patches !== false;
5693
+ if (applyPatches) {
5694
+ const onlyPatchedTreeModules = await serverOps.getSources({
5695
+ ...patchAnalysis,
5696
+ ...patchOps
5697
+ });
5698
+ sourcesRes = {
5699
+ sources: {
5700
+ ...sourcesRes.sources,
5701
+ ...(onlyPatchedTreeModules.sources || {})
5702
+ },
5703
+ errors: {
5704
+ ...sourcesRes.errors,
5705
+ ...(onlyPatchedTreeModules.errors || {})
5706
+ }
5707
+ };
5708
+ }
5709
+ const renderRes = applyPatches ? await serverOps.getRenders(schemasRes, sourcesRes.sources) : {
5710
+ renders: {}
5931
5711
  };
5932
- const renderRes = await serverOps.getRenders(schemasRes, sourcesRes.sources);
5933
5712
  let sourcesValidation = {
5934
5713
  errors: {},
5935
5714
  files: {},
@@ -5977,7 +5756,9 @@ const ValServer = (valModules, options, callbacks) => {
5977
5756
  const hasPatches = (((_patchAnalysis$patche2 = patchAnalysis.patchesByModule[moduleFilePath]) === null || _patchAnalysis$patche2 === void 0 ? void 0 : _patchAnalysis$patche2.length) ?? 0) > 0;
5978
5757
  modules[moduleFilePath] = {
5979
5758
  source: module,
5980
- baseSource: hasPatches ? unpatchedSources[moduleFilePath] : undefined,
5759
+ // baseSource is only meaningful when the server applied patches:
5760
+ // with apply_patches=false, `source` is already un-patched.
5761
+ baseSource: applyPatches && hasPatches ? unpatchedSources[moduleFilePath] : undefined,
5981
5762
  render: renderRes.renders[moduleFilePath] || null,
5982
5763
  patches: appliedPatches.length > 0 || skippedPatches.length > 0 || Object.keys(patchErrors).length > 0 ? {
5983
5764
  applied: appliedPatches,
@@ -1289,6 +1289,11 @@ export default new Proxy({}, {
1289
1289
  value: "export const ValApp = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValApp' in this file`) } } } )"
1290
1290
  };
1291
1291
  }
1292
+ if (modulePath.includes("/ValModulesClient")) {
1293
+ return {
1294
+ value: "export const ValModulesClient = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValModulesClient' in this file`) } } } ); export const useRegisterValModules = () => { throw new Error(`Cannot use 'useRegisterValModules' in this type of file`) };"
1295
+ };
1296
+ }
1292
1297
  return {
1293
1298
  value: moduleLoader.getModule(modulePath)
1294
1299
  };
@@ -1359,6 +1364,11 @@ export default new Proxy({}, {
1359
1364
  value: requestedName
1360
1365
  };
1361
1366
  }
1367
+ if (requestedName.includes("/ValModulesClient")) {
1368
+ return {
1369
+ value: requestedName
1370
+ };
1371
+ }
1362
1372
  const modulePath = moduleLoader.resolveModulePath(baseModuleName, requestedName);
1363
1373
  return {
1364
1374
  value: modulePath
@@ -1493,7 +1503,6 @@ function encodeJwt(payload, sessionKey) {
1493
1503
  }
1494
1504
 
1495
1505
  /* eslint-disable @typescript-eslint/no-unused-vars */
1496
- const textEncoder$2 = new TextEncoder();
1497
1506
  const jsonOps = new patch.JSONOps();
1498
1507
  const tsOps = new TSOps(document => {
1499
1508
  return fp.pipe(analyzeValModule(document), fp.result.map(({
@@ -1525,47 +1534,6 @@ class ValOps {
1525
1534
  this.configSha = null;
1526
1535
  this.modulesErrors = null;
1527
1536
  }
1528
- hash(input) {
1529
- if (typeof input === "object") {
1530
- return this.hashObject(input);
1531
- }
1532
- return core.Internal.getSHA256Hash(textEncoder$2.encode(input));
1533
- }
1534
- hashObject(obj) {
1535
- const collector = [];
1536
- this.collectObjectRecursive(obj, collector);
1537
- return core.Internal.getSHA256Hash(textEncoder$2.encode(collector.join("")));
1538
- }
1539
- collectObjectRecursive(item, collector) {
1540
- if (typeof item === "string") {
1541
- collector.push(`"`, item, `"`);
1542
- return;
1543
- } else if (typeof item === "number") {
1544
- collector.push(item.toString());
1545
- return;
1546
- } else if (typeof item === "object") {
1547
- if (Array.isArray(item)) {
1548
- collector.push("[");
1549
- for (let i = 0; i < item.length; i++) {
1550
- this.collectObjectRecursive(item[i], collector);
1551
- if (i !== item.length - 1) collector.push(",");
1552
- }
1553
- collector.push("]");
1554
- } else {
1555
- collector.push("{");
1556
- const keys = Object.keys(item).sort();
1557
- keys.forEach((key, i) => {
1558
- collector.push(`"${key}":`);
1559
- this.collectObjectRecursive(item[key], collector);
1560
- if (i !== keys.length - 1) collector.push(",");
1561
- });
1562
- collector.push("}");
1563
- }
1564
- return;
1565
- } else {
1566
- console.warn("Unknown type encountered when hashing object", typeof item, item);
1567
- }
1568
- }
1569
1537
 
1570
1538
  // #region stat
1571
1539
  /**
@@ -1581,93 +1549,23 @@ class ValOps {
1581
1549
  // #region initTree
1582
1550
  async initSources() {
1583
1551
  if (this.baseSha === null || this.sourcesSha === null || this.configSha === null || this.schemaSha === null || this.sources === null || this.schemas === null || this.modulesErrors === null) {
1584
- const currentModulesErrors = [];
1585
- const addModuleError = (message, index, path) => {
1586
- currentModulesErrors[index] = {
1587
- message,
1588
- path: path
1589
- };
1552
+ const extracted = await core.extractValModules(this.valModules);
1553
+ this.sources = extracted.sources;
1554
+ this.schemas = extracted.schemas;
1555
+ this.baseSha = extracted.baseSha;
1556
+ this.schemaSha = extracted.schemaSha;
1557
+ this.sourcesSha = extracted.sourcesSha;
1558
+ this.configSha = extracted.configSha;
1559
+ this.modulesErrors = extracted.moduleErrors;
1560
+ return {
1561
+ baseSha: this.baseSha,
1562
+ schemaSha: this.schemaSha,
1563
+ sourcesSha: this.sourcesSha,
1564
+ configSha: this.configSha,
1565
+ sources: extracted.sources,
1566
+ schemas: extracted.schemas,
1567
+ moduleErrors: extracted.moduleErrors
1590
1568
  };
1591
- const currentSources = {};
1592
- const currentSchemas = {};
1593
- const configSha = this.hash(JSON.stringify(this.valModules.config));
1594
- let sourcesSha = "";
1595
- let baseSha = configSha;
1596
- let schemaSha = configSha;
1597
- for (let moduleIdx = 0; moduleIdx < this.valModules.modules.length; moduleIdx++) {
1598
- const module = this.valModules.modules[moduleIdx];
1599
- if (!module.def) {
1600
- addModuleError("val.modules is missing 'def' property", moduleIdx);
1601
- continue;
1602
- }
1603
- if (typeof module.def !== "function") {
1604
- addModuleError("val.modules 'def' property is not a function", moduleIdx);
1605
- continue;
1606
- }
1607
- await module.def().then(value => {
1608
- if (!value) {
1609
- addModuleError(`val.modules 'def' did not return a value`, moduleIdx);
1610
- return;
1611
- }
1612
- if (!value.default) {
1613
- addModuleError(`val.modules 'def' did not return a default export`, moduleIdx);
1614
- return;
1615
- }
1616
- const path = core.Internal.getValPath(value.default);
1617
- if (path === undefined) {
1618
- addModuleError(`path is undefined`, moduleIdx);
1619
- return;
1620
- }
1621
- const schema = core.Internal.getSchema(value.default);
1622
- if (schema === undefined) {
1623
- addModuleError(`schema in path '${path}' is undefined`, moduleIdx, path);
1624
- return;
1625
- }
1626
- if (!(schema instanceof core.Schema)) {
1627
- addModuleError(`schema in path '${path}' is not an instance of Schema`, moduleIdx, path);
1628
- return;
1629
- }
1630
- if (typeof schema["executeSerialize"] !== "function") {
1631
- addModuleError(`schema.serialize in path '${path}' is not a function`, moduleIdx, path);
1632
- return;
1633
- }
1634
- const source = core.Internal.getSource(value.default);
1635
- if (source === undefined) {
1636
- addModuleError(`source in ${path} is undefined`, moduleIdx, path);
1637
- return;
1638
- }
1639
- let serializedSchema;
1640
- try {
1641
- serializedSchema = schema["executeSerialize"]();
1642
- } catch (e) {
1643
- const message = e instanceof Error ? e.message : JSON.stringify(e);
1644
- addModuleError(`Could not serialize module: '${path}'. Error: ${message}`, moduleIdx, path);
1645
- return;
1646
- }
1647
- const pathM = path;
1648
- currentSources[pathM] = source;
1649
- currentSchemas[pathM] = schema;
1650
- // make sure the checks above is enough that this does not fail - even if val modules are not set up correctly
1651
- sourcesSha = this.hash(sourcesSha + JSON.stringify({
1652
- path,
1653
- source
1654
- }));
1655
- baseSha = this.hash(baseSha + JSON.stringify({
1656
- path,
1657
- schema: serializedSchema,
1658
- source,
1659
- modulesErrors: currentModulesErrors
1660
- }));
1661
- schemaSha = this.hash(schemaSha + JSON.stringify(serializedSchema));
1662
- });
1663
- }
1664
- this.sources = currentSources;
1665
- this.schemas = currentSchemas;
1666
- this.baseSha = baseSha;
1667
- this.schemaSha = schemaSha;
1668
- this.sourcesSha = sourcesSha;
1669
- this.configSha = configSha;
1670
- this.modulesErrors = currentModulesErrors;
1671
1569
  }
1672
1570
  return {
1673
1571
  baseSha: this.baseSha,
@@ -1847,79 +1745,6 @@ class ValOps {
1847
1745
 
1848
1746
  // #region validateSources
1849
1747
  async validateSources(schemas, sources, patchesByModule) {
1850
- const checkKeyIsValid = async (key, sourcePath) => {
1851
- var _schemas$moduleFilePa;
1852
- const [moduleFilePath] = core.Internal.splitModuleFilePathAndModulePath(sourcePath);
1853
- const keyOfModuleSource = sources[moduleFilePath];
1854
- const keyOfModuleSchema = (_schemas$moduleFilePa = schemas[moduleFilePath]) === null || _schemas$moduleFilePa === void 0 ? void 0 : _schemas$moduleFilePa["executeSerialize"]();
1855
- if (keyOfModuleSchema && keyOfModuleSchema.type !== "record") {
1856
- return {
1857
- error: true,
1858
- message: `Expected key at ${sourcePath} to be of type 'record'`
1859
- };
1860
- }
1861
- if (keyOfModuleSource && typeof keyOfModuleSource === "object" && key in keyOfModuleSource) {
1862
- return {
1863
- error: false
1864
- };
1865
- }
1866
- if (!keyOfModuleSource || typeof keyOfModuleSource !== "object") {
1867
- return {
1868
- error: true,
1869
- message: `Expected ${sourcePath} to be a truthy object`
1870
- };
1871
- }
1872
- return {
1873
- error: true,
1874
- message: `Key '${key}' does not exist in ${sourcePath}.`
1875
- };
1876
- };
1877
- const checkRouteIsValid = async (route, includePattern, excludePattern) => {
1878
- // Find all router modules (record schemas with router property)
1879
- const routerModules = [];
1880
- for (const [moduleFilePath, schema] of Object.entries(schemas)) {
1881
- const serializedSchema = schema["executeSerialize"]();
1882
- if (serializedSchema.type === "record" && serializedSchema.router) {
1883
- const source = sources[moduleFilePath];
1884
- if (source && typeof source === "object") {
1885
- routerModules.push({
1886
- path: moduleFilePath,
1887
- routes: Object.keys(source)
1888
- });
1889
- }
1890
- }
1891
- }
1892
- if (routerModules.length === 0) {
1893
- return {
1894
- error: true,
1895
- message: `No router modules found. Route validation requires at least one s.record().router() module.`
1896
- };
1897
- }
1898
-
1899
- // Check if route exists in any router module
1900
- const allRoutes = routerModules.flatMap(m => m.routes);
1901
- const routeExists = allRoutes.includes(route);
1902
- if (!routeExists) {
1903
- // Filter routes by include/exclude patterns for suggestions
1904
- const validRoutes = internal.filterRoutesByPatterns(allRoutes, includePattern, excludePattern);
1905
- return {
1906
- error: true,
1907
- message: `Route '${route}' does not exist in any router module. Available routes: ${validRoutes.slice(0, 10).join(", ")}${validRoutes.length > 10 ? "..." : ""}`
1908
- };
1909
- }
1910
-
1911
- // Validate against include/exclude patterns
1912
- const patternValidation = internal.validateRoutePatterns(route, includePattern, excludePattern);
1913
- if (!patternValidation.valid) {
1914
- return {
1915
- error: true,
1916
- message: patternValidation.message
1917
- };
1918
- }
1919
- return {
1920
- error: false
1921
- };
1922
- };
1923
1748
  const errors = {};
1924
1749
  const files = {};
1925
1750
  const remoteFiles = {};
@@ -1927,15 +1752,27 @@ class ValOps {
1927
1752
  // Build a map of gallery directory → [ModuleFilePath, ...] across ALL modules
1928
1753
  // (must include all modules, not just those being validated, since conflicts can come from any module)
1929
1754
  const galleryDirectoryToModules = new Map();
1755
+ // Build a schema/source snapshot so the shared resolver can cross-reference
1756
+ // keyof:check-keys and router:check-route against every module's data.
1757
+ const snapshot = {
1758
+ schemas: {},
1759
+ sources: {}
1760
+ };
1930
1761
  for (const [moduleFilePathS, schema] of entries) {
1762
+ const moduleFilePath = moduleFilePathS;
1931
1763
  const serialized = schema["executeSerialize"]();
1764
+ snapshot.schemas[moduleFilePath] = serialized;
1765
+ const sourceForModule = sources[moduleFilePath];
1766
+ if (sourceForModule !== undefined) {
1767
+ snapshot.sources[moduleFilePath] = sourceForModule;
1768
+ }
1932
1769
  if (serialized.type === "record" && serialized.mediaType && serialized.directory) {
1933
1770
  const dir = serialized.directory;
1934
1771
  const existing = galleryDirectoryToModules.get(dir);
1935
1772
  if (existing) {
1936
- existing.push(moduleFilePathS);
1773
+ existing.push(moduleFilePath);
1937
1774
  } else {
1938
- galleryDirectoryToModules.set(dir, [moduleFilePathS]);
1775
+ galleryDirectoryToModules.set(dir, [moduleFilePath]);
1939
1776
  }
1940
1777
  }
1941
1778
  }
@@ -1993,78 +1830,12 @@ class ValOps {
1993
1830
  }
1994
1831
  } else if ((_validationError$fixe = validationError.fixes) !== null && _validationError$fixe !== void 0 && _validationError$fixe.includes("image:check-remote") || (_validationError$fixe2 = validationError.fixes) !== null && _validationError$fixe2 !== void 0 && _validationError$fixe2.includes("file:check-remote")) {
1995
1832
  remoteFiles[sourcePath] = validationError.value;
1996
- } else if ((_validationError$fixe3 = validationError.fixes) !== null && _validationError$fixe3 !== void 0 && _validationError$fixe3.includes("keyof:check-keys")) {
1997
- const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
1998
- if (!validationError.value) {
1999
- addError({
2000
- message: `Could not find a value for keyOf at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
2001
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2002
- typeError: true
2003
- });
2004
- } else {
2005
- if (typeof validationError.value !== "object") {
2006
- addError({
2007
- message: `Expected keyOf validation error to have a 'value' property of type 'object'. Found: ${typeof validationError.value}. ${TYPE_ERROR_MESSAGE}`,
2008
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2009
- typeError: true
2010
- });
2011
- } else {
2012
- const key = "key" in validationError.value && validationError.value.key;
2013
- const validationErrorSourcePath = "sourcePath" in validationError.value && validationError.value.sourcePath;
2014
- if (typeof key !== "string") {
2015
- addError({
2016
- message: `Expected keyOf validation error 'value' to have property 'key' of type 'string'. Found: ${typeof key}. ${TYPE_ERROR_MESSAGE}`,
2017
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2018
- typeError: true
2019
- });
2020
- } else if (typeof validationErrorSourcePath !== "string") {
2021
- addError({
2022
- message: `Expected keyOf validation error 'value' to have property 'sourcePath' of type 'string'. Found: ${typeof validationErrorSourcePath}. ${TYPE_ERROR_MESSAGE}`,
2023
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
2024
- typeError: true
2025
- });
2026
- } else {
2027
- const res = await checkKeyIsValid(key, validationErrorSourcePath);
2028
- if (res.error) {
2029
- addError({
2030
- message: res.message
2031
- });
2032
- }
2033
- }
2034
- }
2035
- }
2036
- } else if ((_validationError$fixe4 = validationError.fixes) !== null && _validationError$fixe4 !== void 0 && _validationError$fixe4.includes("router:check-route")) {
2037
- const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
2038
- if (!validationError.value) {
2039
- addError({
2040
- message: `Could not find a value for route at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
2041
- typeError: true
2042
- });
2043
- } else {
2044
- if (typeof validationError.value !== "object") {
2045
- addError({
2046
- message: `Expected route validation error to have a 'value' property of type 'object'. Found: ${typeof validationError.value}. ${TYPE_ERROR_MESSAGE}`,
2047
- typeError: true
2048
- });
2049
- } else {
2050
- const route = "route" in validationError.value && validationError.value.route;
2051
- const includePattern = "include" in validationError.value && validationError.value.include;
2052
- const excludePattern = "exclude" in validationError.value && validationError.value.exclude;
2053
- if (typeof route !== "string") {
2054
- addError({
2055
- message: `Expected route validation error 'value' to have property 'route' of type 'string'. Found: ${typeof route}. ${TYPE_ERROR_MESSAGE}`,
2056
- typeError: true
2057
- });
2058
- } else {
2059
- const res = await checkRouteIsValid(route, includePattern && typeof includePattern === "object" && "source" in includePattern && "flags" in includePattern ? includePattern : undefined, excludePattern && typeof excludePattern === "object" && "source" in excludePattern && "flags" in excludePattern ? excludePattern : undefined);
2060
- if (res.error) {
2061
- addError({
2062
- message: res.message
2063
- });
2064
- }
2065
- }
2066
- }
1833
+ } else if ((_validationError$fixe3 = validationError.fixes) !== null && _validationError$fixe3 !== void 0 && _validationError$fixe3.includes("keyof:check-keys") || (_validationError$fixe4 = validationError.fixes) !== null && _validationError$fixe4 !== void 0 && _validationError$fixe4.includes("router:check-route")) {
1834
+ const resolved = internal.resolveSchemaSourceFixForError(validationError, snapshot);
1835
+ if (resolved && resolved.status === "remaining") {
1836
+ addError(resolved.error);
2067
1837
  }
1838
+ // resolved.status === "resolved" → drop silently
2068
1839
  } else if ((_validationError$fixe5 = validationError.fixes) !== null && _validationError$fixe5 !== void 0 && _validationError$fixe5.includes("images:check-unique-folder") || (_validationError$fixe6 = validationError.fixes) !== null && _validationError$fixe6 !== void 0 && _validationError$fixe6.includes("files:check-unique-folder")) {
2069
1840
  const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
2070
1841
  if (!validationError.value || typeof validationError.value !== "object") {
@@ -5915,21 +5686,29 @@ const ValServer = (valModules, options, callbacks) => {
5915
5686
  const schemasRes = await serverOps.getSchemas();
5916
5687
  let sourcesRes = await serverOps.getSources();
5917
5688
  const unpatchedSources = sourcesRes.sources;
5918
- const onlyPatchedTreeModules = await serverOps.getSources({
5919
- ...patchAnalysis,
5920
- ...patchOps
5921
- });
5922
- sourcesRes = {
5923
- sources: {
5924
- ...sourcesRes.sources,
5925
- ...(onlyPatchedTreeModules.sources || {})
5926
- },
5927
- errors: {
5928
- ...sourcesRes.errors,
5929
- ...(onlyPatchedTreeModules.errors || {})
5930
- }
5689
+ // Default to true to keep the legacy contract for older clients.
5690
+ // The studio client always passes false: it owns patch application
5691
+ // and rendering, and treats /sources/~ as a pure un-patched read.
5692
+ const applyPatches = query.apply_patches !== false;
5693
+ if (applyPatches) {
5694
+ const onlyPatchedTreeModules = await serverOps.getSources({
5695
+ ...patchAnalysis,
5696
+ ...patchOps
5697
+ });
5698
+ sourcesRes = {
5699
+ sources: {
5700
+ ...sourcesRes.sources,
5701
+ ...(onlyPatchedTreeModules.sources || {})
5702
+ },
5703
+ errors: {
5704
+ ...sourcesRes.errors,
5705
+ ...(onlyPatchedTreeModules.errors || {})
5706
+ }
5707
+ };
5708
+ }
5709
+ const renderRes = applyPatches ? await serverOps.getRenders(schemasRes, sourcesRes.sources) : {
5710
+ renders: {}
5931
5711
  };
5932
- const renderRes = await serverOps.getRenders(schemasRes, sourcesRes.sources);
5933
5712
  let sourcesValidation = {
5934
5713
  errors: {},
5935
5714
  files: {},
@@ -5977,7 +5756,9 @@ const ValServer = (valModules, options, callbacks) => {
5977
5756
  const hasPatches = (((_patchAnalysis$patche2 = patchAnalysis.patchesByModule[moduleFilePath]) === null || _patchAnalysis$patche2 === void 0 ? void 0 : _patchAnalysis$patche2.length) ?? 0) > 0;
5978
5757
  modules[moduleFilePath] = {
5979
5758
  source: module,
5980
- baseSource: hasPatches ? unpatchedSources[moduleFilePath] : undefined,
5759
+ // baseSource is only meaningful when the server applied patches:
5760
+ // with apply_patches=false, `source` is already un-patched.
5761
+ baseSource: applyPatches && hasPatches ? unpatchedSources[moduleFilePath] : undefined,
5981
5762
  render: renderRes.renders[moduleFilePath] || null,
5982
5763
  patches: appliedPatches.length > 0 || skippedPatches.length > 0 || Object.keys(patchErrors).length > 0 ? {
5983
5764
  applied: appliedPatches,
@@ -1,14 +1,14 @@
1
1
  import { newQuickJSWASMModule } from 'quickjs-emscripten';
2
2
  import ts from 'typescript';
3
3
  import { result, pipe } from '@valbuild/core/fp';
4
- import { FILE_REF_PROP, FILE_REF_SUBTYPE_TAG, VAL_EXTENSION, derefPatch, Internal, Schema, ImageSchema, DEFAULT_CONTENT_HOST } from '@valbuild/core';
4
+ import { FILE_REF_PROP, FILE_REF_SUBTYPE_TAG, VAL_EXTENSION, derefPatch, Internal, Schema, extractValModules, ImageSchema, DEFAULT_CONTENT_HOST } from '@valbuild/core';
5
5
  import { deepEqual, isNotRoot, PatchError, parseAndValidateArrayIndex, applyPatch, JSONOps, deepClone, sourceToPatchPath } from '@valbuild/core/patch';
6
6
  import * as path from 'path';
7
7
  import path__default from 'path';
8
8
  import fs, { promises } from 'fs';
9
9
  import { transform } from 'sucrase';
10
10
  import { VAL_CSS_PATH, VAL_APP_ID, VAL_OVERLAY_ID } from '@valbuild/ui';
11
- import { filterRoutesByPatterns, validateRoutePatterns, getErrorMessageFromUnknownJson, Patch, ParentRef, VAL_ENABLE_COOKIE_NAME, VAL_STATE_COOKIE, VAL_SESSION_COOKIE, Api } from '@valbuild/shared/internal';
11
+ import { resolveSchemaSourceFixForError, getErrorMessageFromUnknownJson, Patch, ParentRef, VAL_ENABLE_COOKIE_NAME, VAL_STATE_COOKIE, VAL_SESSION_COOKIE, Api } from '@valbuild/shared/internal';
12
12
  import { createUIRequestHandler } from '@valbuild/ui/server';
13
13
  import crypto$1 from 'crypto';
14
14
  import { z } from 'zod';
@@ -1258,6 +1258,11 @@ export default new Proxy({}, {
1258
1258
  value: "export const ValApp = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValApp' in this file`) } } } )"
1259
1259
  };
1260
1260
  }
1261
+ if (modulePath.includes("/ValModulesClient")) {
1262
+ return {
1263
+ value: "export const ValModulesClient = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValModulesClient' in this file`) } } } ); export const useRegisterValModules = () => { throw new Error(`Cannot use 'useRegisterValModules' in this type of file`) };"
1264
+ };
1265
+ }
1261
1266
  return {
1262
1267
  value: moduleLoader.getModule(modulePath)
1263
1268
  };
@@ -1328,6 +1333,11 @@ export default new Proxy({}, {
1328
1333
  value: requestedName
1329
1334
  };
1330
1335
  }
1336
+ if (requestedName.includes("/ValModulesClient")) {
1337
+ return {
1338
+ value: requestedName
1339
+ };
1340
+ }
1331
1341
  const modulePath = moduleLoader.resolveModulePath(baseModuleName, requestedName);
1332
1342
  return {
1333
1343
  value: modulePath
@@ -1462,7 +1472,6 @@ function encodeJwt(payload, sessionKey) {
1462
1472
  }
1463
1473
 
1464
1474
  /* eslint-disable @typescript-eslint/no-unused-vars */
1465
- const textEncoder$2 = new TextEncoder();
1466
1475
  const jsonOps = new JSONOps();
1467
1476
  const tsOps = new TSOps(document => {
1468
1477
  return pipe(analyzeValModule(document), result.map(({
@@ -1494,47 +1503,6 @@ class ValOps {
1494
1503
  this.configSha = null;
1495
1504
  this.modulesErrors = null;
1496
1505
  }
1497
- hash(input) {
1498
- if (typeof input === "object") {
1499
- return this.hashObject(input);
1500
- }
1501
- return Internal.getSHA256Hash(textEncoder$2.encode(input));
1502
- }
1503
- hashObject(obj) {
1504
- const collector = [];
1505
- this.collectObjectRecursive(obj, collector);
1506
- return Internal.getSHA256Hash(textEncoder$2.encode(collector.join("")));
1507
- }
1508
- collectObjectRecursive(item, collector) {
1509
- if (typeof item === "string") {
1510
- collector.push(`"`, item, `"`);
1511
- return;
1512
- } else if (typeof item === "number") {
1513
- collector.push(item.toString());
1514
- return;
1515
- } else if (typeof item === "object") {
1516
- if (Array.isArray(item)) {
1517
- collector.push("[");
1518
- for (let i = 0; i < item.length; i++) {
1519
- this.collectObjectRecursive(item[i], collector);
1520
- if (i !== item.length - 1) collector.push(",");
1521
- }
1522
- collector.push("]");
1523
- } else {
1524
- collector.push("{");
1525
- const keys = Object.keys(item).sort();
1526
- keys.forEach((key, i) => {
1527
- collector.push(`"${key}":`);
1528
- this.collectObjectRecursive(item[key], collector);
1529
- if (i !== keys.length - 1) collector.push(",");
1530
- });
1531
- collector.push("}");
1532
- }
1533
- return;
1534
- } else {
1535
- console.warn("Unknown type encountered when hashing object", typeof item, item);
1536
- }
1537
- }
1538
1506
 
1539
1507
  // #region stat
1540
1508
  /**
@@ -1550,93 +1518,23 @@ class ValOps {
1550
1518
  // #region initTree
1551
1519
  async initSources() {
1552
1520
  if (this.baseSha === null || this.sourcesSha === null || this.configSha === null || this.schemaSha === null || this.sources === null || this.schemas === null || this.modulesErrors === null) {
1553
- const currentModulesErrors = [];
1554
- const addModuleError = (message, index, path) => {
1555
- currentModulesErrors[index] = {
1556
- message,
1557
- path: path
1558
- };
1521
+ const extracted = await extractValModules(this.valModules);
1522
+ this.sources = extracted.sources;
1523
+ this.schemas = extracted.schemas;
1524
+ this.baseSha = extracted.baseSha;
1525
+ this.schemaSha = extracted.schemaSha;
1526
+ this.sourcesSha = extracted.sourcesSha;
1527
+ this.configSha = extracted.configSha;
1528
+ this.modulesErrors = extracted.moduleErrors;
1529
+ return {
1530
+ baseSha: this.baseSha,
1531
+ schemaSha: this.schemaSha,
1532
+ sourcesSha: this.sourcesSha,
1533
+ configSha: this.configSha,
1534
+ sources: extracted.sources,
1535
+ schemas: extracted.schemas,
1536
+ moduleErrors: extracted.moduleErrors
1559
1537
  };
1560
- const currentSources = {};
1561
- const currentSchemas = {};
1562
- const configSha = this.hash(JSON.stringify(this.valModules.config));
1563
- let sourcesSha = "";
1564
- let baseSha = configSha;
1565
- let schemaSha = configSha;
1566
- for (let moduleIdx = 0; moduleIdx < this.valModules.modules.length; moduleIdx++) {
1567
- const module = this.valModules.modules[moduleIdx];
1568
- if (!module.def) {
1569
- addModuleError("val.modules is missing 'def' property", moduleIdx);
1570
- continue;
1571
- }
1572
- if (typeof module.def !== "function") {
1573
- addModuleError("val.modules 'def' property is not a function", moduleIdx);
1574
- continue;
1575
- }
1576
- await module.def().then(value => {
1577
- if (!value) {
1578
- addModuleError(`val.modules 'def' did not return a value`, moduleIdx);
1579
- return;
1580
- }
1581
- if (!value.default) {
1582
- addModuleError(`val.modules 'def' did not return a default export`, moduleIdx);
1583
- return;
1584
- }
1585
- const path = Internal.getValPath(value.default);
1586
- if (path === undefined) {
1587
- addModuleError(`path is undefined`, moduleIdx);
1588
- return;
1589
- }
1590
- const schema = Internal.getSchema(value.default);
1591
- if (schema === undefined) {
1592
- addModuleError(`schema in path '${path}' is undefined`, moduleIdx, path);
1593
- return;
1594
- }
1595
- if (!(schema instanceof Schema)) {
1596
- addModuleError(`schema in path '${path}' is not an instance of Schema`, moduleIdx, path);
1597
- return;
1598
- }
1599
- if (typeof schema["executeSerialize"] !== "function") {
1600
- addModuleError(`schema.serialize in path '${path}' is not a function`, moduleIdx, path);
1601
- return;
1602
- }
1603
- const source = Internal.getSource(value.default);
1604
- if (source === undefined) {
1605
- addModuleError(`source in ${path} is undefined`, moduleIdx, path);
1606
- return;
1607
- }
1608
- let serializedSchema;
1609
- try {
1610
- serializedSchema = schema["executeSerialize"]();
1611
- } catch (e) {
1612
- const message = e instanceof Error ? e.message : JSON.stringify(e);
1613
- addModuleError(`Could not serialize module: '${path}'. Error: ${message}`, moduleIdx, path);
1614
- return;
1615
- }
1616
- const pathM = path;
1617
- currentSources[pathM] = source;
1618
- currentSchemas[pathM] = schema;
1619
- // make sure the checks above is enough that this does not fail - even if val modules are not set up correctly
1620
- sourcesSha = this.hash(sourcesSha + JSON.stringify({
1621
- path,
1622
- source
1623
- }));
1624
- baseSha = this.hash(baseSha + JSON.stringify({
1625
- path,
1626
- schema: serializedSchema,
1627
- source,
1628
- modulesErrors: currentModulesErrors
1629
- }));
1630
- schemaSha = this.hash(schemaSha + JSON.stringify(serializedSchema));
1631
- });
1632
- }
1633
- this.sources = currentSources;
1634
- this.schemas = currentSchemas;
1635
- this.baseSha = baseSha;
1636
- this.schemaSha = schemaSha;
1637
- this.sourcesSha = sourcesSha;
1638
- this.configSha = configSha;
1639
- this.modulesErrors = currentModulesErrors;
1640
1538
  }
1641
1539
  return {
1642
1540
  baseSha: this.baseSha,
@@ -1816,79 +1714,6 @@ class ValOps {
1816
1714
 
1817
1715
  // #region validateSources
1818
1716
  async validateSources(schemas, sources, patchesByModule) {
1819
- const checkKeyIsValid = async (key, sourcePath) => {
1820
- var _schemas$moduleFilePa;
1821
- const [moduleFilePath] = Internal.splitModuleFilePathAndModulePath(sourcePath);
1822
- const keyOfModuleSource = sources[moduleFilePath];
1823
- const keyOfModuleSchema = (_schemas$moduleFilePa = schemas[moduleFilePath]) === null || _schemas$moduleFilePa === void 0 ? void 0 : _schemas$moduleFilePa["executeSerialize"]();
1824
- if (keyOfModuleSchema && keyOfModuleSchema.type !== "record") {
1825
- return {
1826
- error: true,
1827
- message: `Expected key at ${sourcePath} to be of type 'record'`
1828
- };
1829
- }
1830
- if (keyOfModuleSource && typeof keyOfModuleSource === "object" && key in keyOfModuleSource) {
1831
- return {
1832
- error: false
1833
- };
1834
- }
1835
- if (!keyOfModuleSource || typeof keyOfModuleSource !== "object") {
1836
- return {
1837
- error: true,
1838
- message: `Expected ${sourcePath} to be a truthy object`
1839
- };
1840
- }
1841
- return {
1842
- error: true,
1843
- message: `Key '${key}' does not exist in ${sourcePath}.`
1844
- };
1845
- };
1846
- const checkRouteIsValid = async (route, includePattern, excludePattern) => {
1847
- // Find all router modules (record schemas with router property)
1848
- const routerModules = [];
1849
- for (const [moduleFilePath, schema] of Object.entries(schemas)) {
1850
- const serializedSchema = schema["executeSerialize"]();
1851
- if (serializedSchema.type === "record" && serializedSchema.router) {
1852
- const source = sources[moduleFilePath];
1853
- if (source && typeof source === "object") {
1854
- routerModules.push({
1855
- path: moduleFilePath,
1856
- routes: Object.keys(source)
1857
- });
1858
- }
1859
- }
1860
- }
1861
- if (routerModules.length === 0) {
1862
- return {
1863
- error: true,
1864
- message: `No router modules found. Route validation requires at least one s.record().router() module.`
1865
- };
1866
- }
1867
-
1868
- // Check if route exists in any router module
1869
- const allRoutes = routerModules.flatMap(m => m.routes);
1870
- const routeExists = allRoutes.includes(route);
1871
- if (!routeExists) {
1872
- // Filter routes by include/exclude patterns for suggestions
1873
- const validRoutes = filterRoutesByPatterns(allRoutes, includePattern, excludePattern);
1874
- return {
1875
- error: true,
1876
- message: `Route '${route}' does not exist in any router module. Available routes: ${validRoutes.slice(0, 10).join(", ")}${validRoutes.length > 10 ? "..." : ""}`
1877
- };
1878
- }
1879
-
1880
- // Validate against include/exclude patterns
1881
- const patternValidation = validateRoutePatterns(route, includePattern, excludePattern);
1882
- if (!patternValidation.valid) {
1883
- return {
1884
- error: true,
1885
- message: patternValidation.message
1886
- };
1887
- }
1888
- return {
1889
- error: false
1890
- };
1891
- };
1892
1717
  const errors = {};
1893
1718
  const files = {};
1894
1719
  const remoteFiles = {};
@@ -1896,15 +1721,27 @@ class ValOps {
1896
1721
  // Build a map of gallery directory → [ModuleFilePath, ...] across ALL modules
1897
1722
  // (must include all modules, not just those being validated, since conflicts can come from any module)
1898
1723
  const galleryDirectoryToModules = new Map();
1724
+ // Build a schema/source snapshot so the shared resolver can cross-reference
1725
+ // keyof:check-keys and router:check-route against every module's data.
1726
+ const snapshot = {
1727
+ schemas: {},
1728
+ sources: {}
1729
+ };
1899
1730
  for (const [moduleFilePathS, schema] of entries) {
1731
+ const moduleFilePath = moduleFilePathS;
1900
1732
  const serialized = schema["executeSerialize"]();
1733
+ snapshot.schemas[moduleFilePath] = serialized;
1734
+ const sourceForModule = sources[moduleFilePath];
1735
+ if (sourceForModule !== undefined) {
1736
+ snapshot.sources[moduleFilePath] = sourceForModule;
1737
+ }
1901
1738
  if (serialized.type === "record" && serialized.mediaType && serialized.directory) {
1902
1739
  const dir = serialized.directory;
1903
1740
  const existing = galleryDirectoryToModules.get(dir);
1904
1741
  if (existing) {
1905
- existing.push(moduleFilePathS);
1742
+ existing.push(moduleFilePath);
1906
1743
  } else {
1907
- galleryDirectoryToModules.set(dir, [moduleFilePathS]);
1744
+ galleryDirectoryToModules.set(dir, [moduleFilePath]);
1908
1745
  }
1909
1746
  }
1910
1747
  }
@@ -1962,78 +1799,12 @@ class ValOps {
1962
1799
  }
1963
1800
  } else if ((_validationError$fixe = validationError.fixes) !== null && _validationError$fixe !== void 0 && _validationError$fixe.includes("image:check-remote") || (_validationError$fixe2 = validationError.fixes) !== null && _validationError$fixe2 !== void 0 && _validationError$fixe2.includes("file:check-remote")) {
1964
1801
  remoteFiles[sourcePath] = validationError.value;
1965
- } else if ((_validationError$fixe3 = validationError.fixes) !== null && _validationError$fixe3 !== void 0 && _validationError$fixe3.includes("keyof:check-keys")) {
1966
- const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
1967
- if (!validationError.value) {
1968
- addError({
1969
- message: `Could not find a value for keyOf at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
1970
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
1971
- typeError: true
1972
- });
1973
- } else {
1974
- if (typeof validationError.value !== "object") {
1975
- addError({
1976
- message: `Expected keyOf validation error to have a 'value' property of type 'object'. Found: ${typeof validationError.value}. ${TYPE_ERROR_MESSAGE}`,
1977
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
1978
- typeError: true
1979
- });
1980
- } else {
1981
- const key = "key" in validationError.value && validationError.value.key;
1982
- const validationErrorSourcePath = "sourcePath" in validationError.value && validationError.value.sourcePath;
1983
- if (typeof key !== "string") {
1984
- addError({
1985
- message: `Expected keyOf validation error 'value' to have property 'key' of type 'string'. Found: ${typeof key}. ${TYPE_ERROR_MESSAGE}`,
1986
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
1987
- typeError: true
1988
- });
1989
- } else if (typeof validationErrorSourcePath !== "string") {
1990
- addError({
1991
- message: `Expected keyOf validation error 'value' to have property 'sourcePath' of type 'string'. Found: ${typeof validationErrorSourcePath}. ${TYPE_ERROR_MESSAGE}`,
1992
- // Not sure this is a type error, but it shouldn't happen in a normally functioning Val system
1993
- typeError: true
1994
- });
1995
- } else {
1996
- const res = await checkKeyIsValid(key, validationErrorSourcePath);
1997
- if (res.error) {
1998
- addError({
1999
- message: res.message
2000
- });
2001
- }
2002
- }
2003
- }
2004
- }
2005
- } else if ((_validationError$fixe4 = validationError.fixes) !== null && _validationError$fixe4 !== void 0 && _validationError$fixe4.includes("router:check-route")) {
2006
- const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
2007
- if (!validationError.value) {
2008
- addError({
2009
- message: `Could not find a value for route at ${sourcePath}. ${TYPE_ERROR_MESSAGE}`,
2010
- typeError: true
2011
- });
2012
- } else {
2013
- if (typeof validationError.value !== "object") {
2014
- addError({
2015
- message: `Expected route validation error to have a 'value' property of type 'object'. Found: ${typeof validationError.value}. ${TYPE_ERROR_MESSAGE}`,
2016
- typeError: true
2017
- });
2018
- } else {
2019
- const route = "route" in validationError.value && validationError.value.route;
2020
- const includePattern = "include" in validationError.value && validationError.value.include;
2021
- const excludePattern = "exclude" in validationError.value && validationError.value.exclude;
2022
- if (typeof route !== "string") {
2023
- addError({
2024
- message: `Expected route validation error 'value' to have property 'route' of type 'string'. Found: ${typeof route}. ${TYPE_ERROR_MESSAGE}`,
2025
- typeError: true
2026
- });
2027
- } else {
2028
- const res = await checkRouteIsValid(route, includePattern && typeof includePattern === "object" && "source" in includePattern && "flags" in includePattern ? includePattern : undefined, excludePattern && typeof excludePattern === "object" && "source" in excludePattern && "flags" in excludePattern ? excludePattern : undefined);
2029
- if (res.error) {
2030
- addError({
2031
- message: res.message
2032
- });
2033
- }
2034
- }
2035
- }
1802
+ } else if ((_validationError$fixe3 = validationError.fixes) !== null && _validationError$fixe3 !== void 0 && _validationError$fixe3.includes("keyof:check-keys") || (_validationError$fixe4 = validationError.fixes) !== null && _validationError$fixe4 !== void 0 && _validationError$fixe4.includes("router:check-route")) {
1803
+ const resolved = resolveSchemaSourceFixForError(validationError, snapshot);
1804
+ if (resolved && resolved.status === "remaining") {
1805
+ addError(resolved.error);
2036
1806
  }
1807
+ // resolved.status === "resolved" → drop silently
2037
1808
  } else if ((_validationError$fixe5 = validationError.fixes) !== null && _validationError$fixe5 !== void 0 && _validationError$fixe5.includes("images:check-unique-folder") || (_validationError$fixe6 = validationError.fixes) !== null && _validationError$fixe6 !== void 0 && _validationError$fixe6.includes("files:check-unique-folder")) {
2038
1809
  const TYPE_ERROR_MESSAGE = `This is most likely a Val version mismatch or Val bug.`;
2039
1810
  if (!validationError.value || typeof validationError.value !== "object") {
@@ -5884,21 +5655,29 @@ const ValServer = (valModules, options, callbacks) => {
5884
5655
  const schemasRes = await serverOps.getSchemas();
5885
5656
  let sourcesRes = await serverOps.getSources();
5886
5657
  const unpatchedSources = sourcesRes.sources;
5887
- const onlyPatchedTreeModules = await serverOps.getSources({
5888
- ...patchAnalysis,
5889
- ...patchOps
5890
- });
5891
- sourcesRes = {
5892
- sources: {
5893
- ...sourcesRes.sources,
5894
- ...(onlyPatchedTreeModules.sources || {})
5895
- },
5896
- errors: {
5897
- ...sourcesRes.errors,
5898
- ...(onlyPatchedTreeModules.errors || {})
5899
- }
5658
+ // Default to true to keep the legacy contract for older clients.
5659
+ // The studio client always passes false: it owns patch application
5660
+ // and rendering, and treats /sources/~ as a pure un-patched read.
5661
+ const applyPatches = query.apply_patches !== false;
5662
+ if (applyPatches) {
5663
+ const onlyPatchedTreeModules = await serverOps.getSources({
5664
+ ...patchAnalysis,
5665
+ ...patchOps
5666
+ });
5667
+ sourcesRes = {
5668
+ sources: {
5669
+ ...sourcesRes.sources,
5670
+ ...(onlyPatchedTreeModules.sources || {})
5671
+ },
5672
+ errors: {
5673
+ ...sourcesRes.errors,
5674
+ ...(onlyPatchedTreeModules.errors || {})
5675
+ }
5676
+ };
5677
+ }
5678
+ const renderRes = applyPatches ? await serverOps.getRenders(schemasRes, sourcesRes.sources) : {
5679
+ renders: {}
5900
5680
  };
5901
- const renderRes = await serverOps.getRenders(schemasRes, sourcesRes.sources);
5902
5681
  let sourcesValidation = {
5903
5682
  errors: {},
5904
5683
  files: {},
@@ -5946,7 +5725,9 @@ const ValServer = (valModules, options, callbacks) => {
5946
5725
  const hasPatches = (((_patchAnalysis$patche2 = patchAnalysis.patchesByModule[moduleFilePath]) === null || _patchAnalysis$patche2 === void 0 ? void 0 : _patchAnalysis$patche2.length) ?? 0) > 0;
5947
5726
  modules[moduleFilePath] = {
5948
5727
  source: module,
5949
- baseSource: hasPatches ? unpatchedSources[moduleFilePath] : undefined,
5728
+ // baseSource is only meaningful when the server applied patches:
5729
+ // with apply_patches=false, `source` is already un-patched.
5730
+ baseSource: applyPatches && hasPatches ? unpatchedSources[moduleFilePath] : undefined,
5950
5731
  render: renderRes.renders[moduleFilePath] || null,
5951
5732
  patches: appliedPatches.length > 0 || skippedPatches.length > 0 || Object.keys(patchErrors).length > 0 ? {
5952
5733
  applied: appliedPatches,
package/package.json CHANGED
@@ -16,7 +16,7 @@
16
16
  "./package.json": "./package.json"
17
17
  },
18
18
  "types": "dist/valbuild-server.cjs.d.ts",
19
- "version": "0.97.0",
19
+ "version": "0.97.2",
20
20
  "devDependencies": {
21
21
  "@prettier/sync": "^0.6.1",
22
22
  "@types/jest": "^30.0.0"
@@ -30,9 +30,9 @@
30
30
  "typescript": "^5.9.3",
31
31
  "zod": "^4.3.5",
32
32
  "zod-validation-error": "^5.0.0",
33
- "@valbuild/core": "0.97.0",
34
- "@valbuild/shared": "0.97.0",
35
- "@valbuild/ui": "0.97.0"
33
+ "@valbuild/core": "0.97.1",
34
+ "@valbuild/shared": "0.97.1",
35
+ "@valbuild/ui": "0.97.2"
36
36
  },
37
37
  "engines": {
38
38
  "node": ">=18.17.0"