@valbuild/server 0.60.18 → 0.60.19

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.
@@ -42,7 +42,11 @@ export declare class LocalValServer extends ValServer {
42
42
  private getPatchFilePath;
43
43
  private badRequest;
44
44
  protected ensureRemoteFSInitialized(): Promise<result.Result<undefined, ValServerError>>;
45
- protected getModule(moduleId: ModuleId): Promise<SerializedModuleContent>;
45
+ protected getModule(moduleId: ModuleId, options: {
46
+ validate: boolean;
47
+ source: boolean;
48
+ schema: boolean;
49
+ }): Promise<SerializedModuleContent>;
46
50
  protected execCommit(patches: [PatchId, ModuleId, Patch][]): Promise<{
47
51
  status: 200;
48
52
  json: Record<ModuleId, {
@@ -25,7 +25,11 @@ export declare class Service {
25
25
  private readonly runtime;
26
26
  readonly valConfigPath: string;
27
27
  constructor({ valConfigPath }: ServiceOptions, sourceFileHandler: ValSourceFileHandler, runtime: QuickJSRuntime);
28
- get(moduleId: ModuleId, modulePath?: ModulePath): Promise<SerializedModuleContent>;
28
+ get(moduleId: ModuleId, modulePath: ModulePath, options?: {
29
+ validate: boolean;
30
+ source: boolean;
31
+ schema: boolean;
32
+ }): Promise<SerializedModuleContent>;
29
33
  patch(moduleId: ModuleId, patch: Patch): Promise<void>;
30
34
  dispose(): void;
31
35
  }
@@ -32,6 +32,7 @@ export declare abstract class ValServer implements IValServer {
32
32
  patch?: string;
33
33
  schema?: string;
34
34
  source?: string;
35
+ validate?: string;
35
36
  }, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiTreeResponse>>;
36
37
  postValidate(rawBody: unknown, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiPostValidationResponse | ApiPostValidationErrorResponse>>;
37
38
  postCommit(rawBody: unknown, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiCommitResponse, ApiPostValidationErrorResponse>>;
@@ -58,7 +59,11 @@ export declare abstract class ValServer implements IValServer {
58
59
  * 2) The error is returned via API if the remote FS could not be initialized
59
60
  * */
60
61
  protected abstract ensureRemoteFSInitialized(errorMessageType: string, cookies: ValCookies<VAL_SESSION_COOKIE>): Promise<result.Result<undefined, ValServerError>>;
61
- protected abstract getModule(moduleId: ModuleId): Promise<SerializedModuleContent>;
62
+ protected abstract getModule(moduleId: ModuleId, options: {
63
+ validate: boolean;
64
+ source: boolean;
65
+ schema: boolean;
66
+ }): Promise<SerializedModuleContent>;
62
67
  protected abstract execCommit(patches: [PatchId, ModuleId, Patch][], cookies: ValCookies<VAL_SESSION_COOKIE>): Promise<{
63
68
  status: 200;
64
69
  json: Record<ModuleId, {
@@ -129,6 +134,7 @@ export interface IValServer {
129
134
  patch?: string;
130
135
  schema?: string;
131
136
  source?: string;
137
+ validate?: string;
132
138
  }, cookies: ValCookies<VAL_SESSION_COOKIE>, requestHeaders: RequestHeaders): Promise<ValServerJsonResult<ApiTreeResponse>>;
133
139
  getPatches(query: {
134
140
  id?: string[];
@@ -10,6 +10,7 @@ var patch = require('@valbuild/core/patch');
10
10
  var path = require('path');
11
11
  var fs = require('fs');
12
12
  var sucrase = require('sucrase');
13
+ var ui = require('@valbuild/ui');
13
14
  var z = require('zod');
14
15
  var internal = require('@valbuild/shared/internal');
15
16
  var sizeOf = require('image-size');
@@ -692,7 +693,7 @@ const patchSourceFile = (sourceFile, patch$1) => {
692
693
  return patch.applyPatch(sourceFile, ops$1, patch$1);
693
694
  };
694
695
 
695
- const readValFile = async (id, valConfigPath, runtime) => {
696
+ const readValFile = async (id, valConfigPath, runtime, options) => {
696
697
  const context = runtime.newContext();
697
698
 
698
699
  // avoid failures when console.log is called
@@ -710,8 +711,22 @@ const readValFile = async (id, valConfigPath, runtime) => {
710
711
  const processHandle = context.newObject();
711
712
  context.setProp(processHandle, "env", envHandle);
712
713
  context.setProp(context.global, "process", processHandle);
714
+ const optionsHandle = context.newObject();
715
+ if (options) {
716
+ if (options.validate !== undefined) {
717
+ context.setProp(optionsHandle, "validate", context.newNumber(+options.validate));
718
+ }
719
+ if (options.source !== undefined) {
720
+ context.setProp(optionsHandle, "source", context.newNumber(+options.source));
721
+ }
722
+ if (options.schema !== undefined) {
723
+ context.setProp(optionsHandle, "schema", context.newNumber(+options.schema));
724
+ }
725
+ }
726
+ context.setProp(context.global, "__VAL_OPTIONS__", optionsHandle);
713
727
  envHandle.dispose();
714
728
  processHandle.dispose();
729
+ optionsHandle.dispose();
715
730
  try {
716
731
  const modulePath = `.${id}.val`;
717
732
  const code = `import * as valModule from ${JSON.stringify(modulePath)};
@@ -719,12 +734,12 @@ import { Internal } from "@valbuild/core";
719
734
 
720
735
  globalThis.valModule = {
721
736
  id: valModule?.default && Internal.getValPath(valModule?.default),
722
- schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
723
- source: valModule?.default && Internal.getSource(valModule?.default),
724
- validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
737
+ schema: !!globalThis['__VAL_OPTIONS__'].schema ? valModule?.default && Internal.getSchema(valModule?.default)?.serialize() : undefined,
738
+ source: !!globalThis['__VAL_OPTIONS__'].source ? valModule?.default && Internal.getSource(valModule?.default) : undefined,
739
+ validation: !!globalThis['__VAL_OPTIONS__'].validate ? valModule?.default && Internal.getSchema(valModule?.default)?.validate(
725
740
  valModule?.default && Internal.getValPath(valModule?.default) || "/",
726
741
  valModule?.default && Internal.getSource(valModule?.default)
727
- ),
742
+ ) : undefined,
728
743
  defaultExport: !!valModule?.default,
729
744
  };
730
745
  `;
@@ -755,9 +770,9 @@ globalThis.valModule = {
755
770
  fatalErrors.push(`Wrong c.define id! Expected: '${id}', found: '${valModule.id}'`);
756
771
  } else if (encodeURIComponent(valModule.id).replace(/%2F/g, "/") !== valModule.id) {
757
772
  fatalErrors.push(`Invalid c.define id! Must be a web-safe path without escape characters, found: '${valModule.id}', which was encoded as: '${encodeURIComponent(valModule.id).replace("%2F", "/")}'`);
758
- } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined) {
773
+ } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined && options.schema) {
759
774
  fatalErrors.push(`Expected val id: '${id}' to have a schema`);
760
- } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
775
+ } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined && options.source) {
761
776
  fatalErrors.push(`Expected val id: '${id}' to have a source`);
762
777
  }
763
778
  }
@@ -998,7 +1013,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
998
1013
  }
999
1014
  if (modulePath === "@valbuild/react/internal") {
1000
1015
  return {
1001
- value: "export const useVal = () => { throw Error(`Cannot use 'useVal' in this type of file`) }; export function ValProvider() { throw Error(`Cannot use 'ValProvider' in this type of file`) }; export function ValRichText() { throw Error(`Cannot use 'ValRichText' in this type of file`)};"
1016
+ value: `
1017
+ const useVal = () => { throw Error('Cannot use \\'useVal\\' in this type of file') };
1018
+ export function ValProvider() { throw Error('Cannot use \\'ValProvider\\' in this type of file') };
1019
+ export function ValRichText() { throw Error('Cannot use \\'ValRichText\\' in this type of file')};`
1002
1020
  };
1003
1021
  }
1004
1022
  if (modulePath === "@valbuild/ui") {
@@ -1007,10 +1025,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
1007
1025
  export const ValOverlay = () => {
1008
1026
  throw Error("Cannot use 'ValOverlay' in this type of file")
1009
1027
  };
1010
- export const VAL_CSS_PATH = "/spa/index.css";
1011
- export const VAL_APP_PATH = "/app";
1012
- export const VAL_APP_ID = "val-app";
1013
- export const VAL_OVERLAY_ID = "val-overlay";
1028
+ export const VAL_CSS_PATH = "${ui.VAL_CSS_PATH}";
1029
+ export const VAL_APP_PATH = "${ui.VAL_CSS_PATH}";
1030
+ export const VAL_APP_ID = "${ui.VAL_APP_ID}";
1031
+ export const VAL_OVERLAY_ID = "${ui.VAL_OVERLAY_ID}";
1014
1032
  export const IS_DEV = false;
1015
1033
  `
1016
1034
  };
@@ -1037,7 +1055,21 @@ export const IS_DEV = false;
1037
1055
  }
1038
1056
  if (modulePath.startsWith("react")) {
1039
1057
  return {
1040
- value: "export const createContext = () => new Proxy({}, { get() { return () => { throw new Error(`Cannot use 'createContext' in this file`) } } } ); export const useTransition = () => { throw Error(`Cannot use 'useTransition' in this type of file`) }; export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'react' in this file`) } } } )"
1058
+ value: `
1059
+ export const createContext = () => new Proxy({}, { get() { return () => { throw new Error('Cannot use \\'createContext\\' in this file') } } } );
1060
+ export const useTransition = () => { throw Error('Cannot use \\'useTransition\\' in this type of file') };
1061
+
1062
+ export default new Proxy({}, {
1063
+ get(target, props) {
1064
+ // React.createContext might be called on top-level
1065
+ if (props === 'createContext') {
1066
+ return createContext;
1067
+ }
1068
+ return () => {
1069
+ throw new Error('Cannot import \\'react\\' in this file');
1070
+ }
1071
+ }
1072
+ })`
1041
1073
  };
1042
1074
  }
1043
1075
  if (modulePath.includes("/ValNextProvider")) {
@@ -1168,8 +1200,12 @@ class Service {
1168
1200
  this.runtime = runtime;
1169
1201
  this.valConfigPath = valConfigPath || "./val.config";
1170
1202
  }
1171
- async get(moduleId, modulePath = "") {
1172
- const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
1203
+ async get(moduleId, modulePath, options) {
1204
+ const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
1205
+ validate: true,
1206
+ source: true,
1207
+ schema: true
1208
+ });
1173
1209
  if (valModule.source && valModule.schema) {
1174
1210
  const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
1175
1211
  const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
@@ -1434,8 +1470,11 @@ class ValServer {
1434
1470
  if (fp.result.isErr(ensureRes)) {
1435
1471
  return ensureRes.error;
1436
1472
  }
1437
- const moduleIds = this.getAllModules(treePath);
1438
1473
  const applyPatches = query.patch === "true";
1474
+ const execValidations = query.validate === "true";
1475
+ const includeSource = query.source === "true";
1476
+ const includeSchema = query.schema === "true";
1477
+ const moduleIds = this.getAllModules(treePath);
1439
1478
  let {
1440
1479
  patchIdsByModuleId,
1441
1480
  patchesById,
@@ -1455,7 +1494,7 @@ class ValServer {
1455
1494
  fileUpdates = res.value.fileUpdates;
1456
1495
  }
1457
1496
  const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
1458
- return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders);
1497
+ return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
1459
1498
  }));
1460
1499
  const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
1461
1500
  const module = {
@@ -1509,13 +1548,17 @@ class ValServer {
1509
1548
 
1510
1549
  /* */
1511
1550
 
1512
- async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders) {
1513
- const serializedModuleContent = await this.getModule(moduleId);
1514
- const schema = serializedModuleContent.schema;
1515
- const maybeSource = serializedModuleContent.source;
1551
+ async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
1552
+ const serializedModuleContent = await this.getModule(moduleId, {
1553
+ validate: validate,
1554
+ source: includeSource,
1555
+ schema: includeSchema
1556
+ });
1516
1557
  if (!applyPatches) {
1517
1558
  return serializedModuleContent;
1518
1559
  }
1560
+ const schema = serializedModuleContent.schema;
1561
+ const maybeSource = serializedModuleContent.source;
1519
1562
  if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
1520
1563
  return serializedModuleContent;
1521
1564
  }
@@ -1551,17 +1594,19 @@ class ValServer {
1551
1594
  };
1552
1595
  }
1553
1596
  }
1554
- const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
1555
- if (validationErrors) {
1556
- const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1557
- return {
1558
- path: moduleId,
1559
- schema,
1560
- source,
1561
- errors: revalidated && {
1562
- validation: revalidated
1563
- }
1564
- };
1597
+ if (validate) {
1598
+ const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
1599
+ if (validationErrors) {
1600
+ const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1601
+ return {
1602
+ path: moduleId,
1603
+ schema,
1604
+ source,
1605
+ errors: revalidated && {
1606
+ validation: revalidated
1607
+ }
1608
+ };
1609
+ }
1565
1610
  }
1566
1611
  return {
1567
1612
  path: moduleId,
@@ -1576,7 +1621,7 @@ class ValServer {
1576
1621
  // The reason is that validate will be called inside QuickJS (in the future, hopefully),
1577
1622
  // which does not have access to the filesystem, at least not at the time of writing this comment.
1578
1623
  // If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
1579
- async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders) {
1624
+ async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
1580
1625
  const revalidatedValidationErrors = {};
1581
1626
  for (const pathStr in validationErrors) {
1582
1627
  const errorSourcePath = pathStr;
@@ -1598,7 +1643,7 @@ class ValServer {
1598
1643
  if (updatedFileMetadata) {
1599
1644
  const fileRes = await this.getFiles(fileRef, {
1600
1645
  sha256: updatedFileMetadata.sha256
1601
- }, cookies, requestHeaders);
1646
+ }, cookies, reqHeaders);
1602
1647
  if (fileRes.status === 200 && fileRes.body) {
1603
1648
  const res = new Response(fileRes.body);
1604
1649
  fileBuffer = Buffer.from(await res.arrayBuffer());
@@ -1608,14 +1653,23 @@ class ValServer {
1608
1653
  });
1609
1654
  }
1610
1655
  }
1611
- if (!fileBuffer) {
1612
- try {
1613
- fileBuffer = await this.readStaticBinaryFile(filePath);
1614
- } catch (err) {
1615
- console.error("Val: unexpected error while reading image / file:", filePath, {
1616
- error: err
1656
+ // try fetch file directly via http
1657
+ if (fileRef.startsWith("/public")) {
1658
+ const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
1659
+ const fileUrl = fileRef.slice("/public".length);
1660
+ const fetchRes = await fetch(new URL(fileUrl, host));
1661
+ if (fetchRes.status === 200) {
1662
+ fileBuffer = Buffer.from(await fetchRes.arrayBuffer());
1663
+ } else {
1664
+ console.error("Val: unexpected error while fetching image / file:", fileRef, {
1665
+ error: {
1666
+ status: fetchRes.status,
1667
+ url: fetchRes.url
1668
+ }
1617
1669
  });
1618
1670
  }
1671
+ } else {
1672
+ console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
1619
1673
  }
1620
1674
  if (!fileBuffer) {
1621
1675
  revalidatedValidationErrors[errorSourcePath].push({
@@ -1762,7 +1816,7 @@ class ValServer {
1762
1816
  const moduleId = moduleIdStr;
1763
1817
  const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
1764
1818
  // TODO: refine to ModuleId and PatchId when parsing
1765
- patchIdsByModuleId, patchesById, fileUpdates, true, cookies, requestHeaders);
1819
+ patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
1766
1820
  if (serializedModuleContent.errors) {
1767
1821
  validationErrorsByModuleId[moduleId] = serializedModuleContent;
1768
1822
  }
@@ -2266,8 +2320,8 @@ class LocalValServer extends ValServer {
2266
2320
  // No RemoteFS so nothing to ensure
2267
2321
  return fp.result.ok(undefined);
2268
2322
  }
2269
- getModule(moduleId) {
2270
- return this.options.service.get(moduleId);
2323
+ getModule(moduleId, options) {
2324
+ return this.options.service.get(moduleId, "", options);
2271
2325
  }
2272
2326
  async execCommit(patches) {
2273
2327
  for (const [patchId, moduleId, patch] of patches) {
@@ -2555,11 +2609,11 @@ class ProxyValServer extends ValServer {
2555
2609
 
2556
2610
  /** Remote FS dependent methods: */
2557
2611
 
2558
- async getModule(moduleId) {
2612
+ async getModule(moduleId, options) {
2559
2613
  if (!this.lazyService) {
2560
2614
  this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
2561
2615
  }
2562
- return this.lazyService.get(moduleId);
2616
+ return this.lazyService.get(moduleId, "", options);
2563
2617
  }
2564
2618
  execCommit(patches, cookies) {
2565
2619
  return withAuth(this.options.valSecret, cookies, "execCommit", async ({
@@ -3421,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
3421
3475
  return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
3422
3476
  patch: url.searchParams.get("patch") || undefined,
3423
3477
  schema: url.searchParams.get("schema") || undefined,
3424
- source: url.searchParams.get("source") || undefined
3478
+ source: url.searchParams.get("source") || undefined,
3479
+ validate: url.searchParams.get("validate") || undefined
3425
3480
  }, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
3426
3481
  } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
3427
3482
  return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
@@ -10,6 +10,7 @@ var patch = require('@valbuild/core/patch');
10
10
  var path = require('path');
11
11
  var fs = require('fs');
12
12
  var sucrase = require('sucrase');
13
+ var ui = require('@valbuild/ui');
13
14
  var z = require('zod');
14
15
  var internal = require('@valbuild/shared/internal');
15
16
  var sizeOf = require('image-size');
@@ -692,7 +693,7 @@ const patchSourceFile = (sourceFile, patch$1) => {
692
693
  return patch.applyPatch(sourceFile, ops$1, patch$1);
693
694
  };
694
695
 
695
- const readValFile = async (id, valConfigPath, runtime) => {
696
+ const readValFile = async (id, valConfigPath, runtime, options) => {
696
697
  const context = runtime.newContext();
697
698
 
698
699
  // avoid failures when console.log is called
@@ -710,8 +711,22 @@ const readValFile = async (id, valConfigPath, runtime) => {
710
711
  const processHandle = context.newObject();
711
712
  context.setProp(processHandle, "env", envHandle);
712
713
  context.setProp(context.global, "process", processHandle);
714
+ const optionsHandle = context.newObject();
715
+ if (options) {
716
+ if (options.validate !== undefined) {
717
+ context.setProp(optionsHandle, "validate", context.newNumber(+options.validate));
718
+ }
719
+ if (options.source !== undefined) {
720
+ context.setProp(optionsHandle, "source", context.newNumber(+options.source));
721
+ }
722
+ if (options.schema !== undefined) {
723
+ context.setProp(optionsHandle, "schema", context.newNumber(+options.schema));
724
+ }
725
+ }
726
+ context.setProp(context.global, "__VAL_OPTIONS__", optionsHandle);
713
727
  envHandle.dispose();
714
728
  processHandle.dispose();
729
+ optionsHandle.dispose();
715
730
  try {
716
731
  const modulePath = `.${id}.val`;
717
732
  const code = `import * as valModule from ${JSON.stringify(modulePath)};
@@ -719,12 +734,12 @@ import { Internal } from "@valbuild/core";
719
734
 
720
735
  globalThis.valModule = {
721
736
  id: valModule?.default && Internal.getValPath(valModule?.default),
722
- schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
723
- source: valModule?.default && Internal.getSource(valModule?.default),
724
- validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
737
+ schema: !!globalThis['__VAL_OPTIONS__'].schema ? valModule?.default && Internal.getSchema(valModule?.default)?.serialize() : undefined,
738
+ source: !!globalThis['__VAL_OPTIONS__'].source ? valModule?.default && Internal.getSource(valModule?.default) : undefined,
739
+ validation: !!globalThis['__VAL_OPTIONS__'].validate ? valModule?.default && Internal.getSchema(valModule?.default)?.validate(
725
740
  valModule?.default && Internal.getValPath(valModule?.default) || "/",
726
741
  valModule?.default && Internal.getSource(valModule?.default)
727
- ),
742
+ ) : undefined,
728
743
  defaultExport: !!valModule?.default,
729
744
  };
730
745
  `;
@@ -755,9 +770,9 @@ globalThis.valModule = {
755
770
  fatalErrors.push(`Wrong c.define id! Expected: '${id}', found: '${valModule.id}'`);
756
771
  } else if (encodeURIComponent(valModule.id).replace(/%2F/g, "/") !== valModule.id) {
757
772
  fatalErrors.push(`Invalid c.define id! Must be a web-safe path without escape characters, found: '${valModule.id}', which was encoded as: '${encodeURIComponent(valModule.id).replace("%2F", "/")}'`);
758
- } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined) {
773
+ } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined && options.schema) {
759
774
  fatalErrors.push(`Expected val id: '${id}' to have a schema`);
760
- } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
775
+ } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined && options.source) {
761
776
  fatalErrors.push(`Expected val id: '${id}' to have a source`);
762
777
  }
763
778
  }
@@ -998,7 +1013,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
998
1013
  }
999
1014
  if (modulePath === "@valbuild/react/internal") {
1000
1015
  return {
1001
- value: "export const useVal = () => { throw Error(`Cannot use 'useVal' in this type of file`) }; export function ValProvider() { throw Error(`Cannot use 'ValProvider' in this type of file`) }; export function ValRichText() { throw Error(`Cannot use 'ValRichText' in this type of file`)};"
1016
+ value: `
1017
+ const useVal = () => { throw Error('Cannot use \\'useVal\\' in this type of file') };
1018
+ export function ValProvider() { throw Error('Cannot use \\'ValProvider\\' in this type of file') };
1019
+ export function ValRichText() { throw Error('Cannot use \\'ValRichText\\' in this type of file')};`
1002
1020
  };
1003
1021
  }
1004
1022
  if (modulePath === "@valbuild/ui") {
@@ -1007,10 +1025,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
1007
1025
  export const ValOverlay = () => {
1008
1026
  throw Error("Cannot use 'ValOverlay' in this type of file")
1009
1027
  };
1010
- export const VAL_CSS_PATH = "/spa/index.css";
1011
- export const VAL_APP_PATH = "/app";
1012
- export const VAL_APP_ID = "val-app";
1013
- export const VAL_OVERLAY_ID = "val-overlay";
1028
+ export const VAL_CSS_PATH = "${ui.VAL_CSS_PATH}";
1029
+ export const VAL_APP_PATH = "${ui.VAL_CSS_PATH}";
1030
+ export const VAL_APP_ID = "${ui.VAL_APP_ID}";
1031
+ export const VAL_OVERLAY_ID = "${ui.VAL_OVERLAY_ID}";
1014
1032
  export const IS_DEV = false;
1015
1033
  `
1016
1034
  };
@@ -1037,7 +1055,21 @@ export const IS_DEV = false;
1037
1055
  }
1038
1056
  if (modulePath.startsWith("react")) {
1039
1057
  return {
1040
- value: "export const createContext = () => new Proxy({}, { get() { return () => { throw new Error(`Cannot use 'createContext' in this file`) } } } ); export const useTransition = () => { throw Error(`Cannot use 'useTransition' in this type of file`) }; export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'react' in this file`) } } } )"
1058
+ value: `
1059
+ export const createContext = () => new Proxy({}, { get() { return () => { throw new Error('Cannot use \\'createContext\\' in this file') } } } );
1060
+ export const useTransition = () => { throw Error('Cannot use \\'useTransition\\' in this type of file') };
1061
+
1062
+ export default new Proxy({}, {
1063
+ get(target, props) {
1064
+ // React.createContext might be called on top-level
1065
+ if (props === 'createContext') {
1066
+ return createContext;
1067
+ }
1068
+ return () => {
1069
+ throw new Error('Cannot import \\'react\\' in this file');
1070
+ }
1071
+ }
1072
+ })`
1041
1073
  };
1042
1074
  }
1043
1075
  if (modulePath.includes("/ValNextProvider")) {
@@ -1168,8 +1200,12 @@ class Service {
1168
1200
  this.runtime = runtime;
1169
1201
  this.valConfigPath = valConfigPath || "./val.config";
1170
1202
  }
1171
- async get(moduleId, modulePath = "") {
1172
- const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
1203
+ async get(moduleId, modulePath, options) {
1204
+ const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
1205
+ validate: true,
1206
+ source: true,
1207
+ schema: true
1208
+ });
1173
1209
  if (valModule.source && valModule.schema) {
1174
1210
  const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
1175
1211
  const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
@@ -1434,8 +1470,11 @@ class ValServer {
1434
1470
  if (fp.result.isErr(ensureRes)) {
1435
1471
  return ensureRes.error;
1436
1472
  }
1437
- const moduleIds = this.getAllModules(treePath);
1438
1473
  const applyPatches = query.patch === "true";
1474
+ const execValidations = query.validate === "true";
1475
+ const includeSource = query.source === "true";
1476
+ const includeSchema = query.schema === "true";
1477
+ const moduleIds = this.getAllModules(treePath);
1439
1478
  let {
1440
1479
  patchIdsByModuleId,
1441
1480
  patchesById,
@@ -1455,7 +1494,7 @@ class ValServer {
1455
1494
  fileUpdates = res.value.fileUpdates;
1456
1495
  }
1457
1496
  const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
1458
- return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders);
1497
+ return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
1459
1498
  }));
1460
1499
  const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
1461
1500
  const module = {
@@ -1509,13 +1548,17 @@ class ValServer {
1509
1548
 
1510
1549
  /* */
1511
1550
 
1512
- async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders) {
1513
- const serializedModuleContent = await this.getModule(moduleId);
1514
- const schema = serializedModuleContent.schema;
1515
- const maybeSource = serializedModuleContent.source;
1551
+ async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
1552
+ const serializedModuleContent = await this.getModule(moduleId, {
1553
+ validate: validate,
1554
+ source: includeSource,
1555
+ schema: includeSchema
1556
+ });
1516
1557
  if (!applyPatches) {
1517
1558
  return serializedModuleContent;
1518
1559
  }
1560
+ const schema = serializedModuleContent.schema;
1561
+ const maybeSource = serializedModuleContent.source;
1519
1562
  if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
1520
1563
  return serializedModuleContent;
1521
1564
  }
@@ -1551,17 +1594,19 @@ class ValServer {
1551
1594
  };
1552
1595
  }
1553
1596
  }
1554
- const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
1555
- if (validationErrors) {
1556
- const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1557
- return {
1558
- path: moduleId,
1559
- schema,
1560
- source,
1561
- errors: revalidated && {
1562
- validation: revalidated
1563
- }
1564
- };
1597
+ if (validate) {
1598
+ const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
1599
+ if (validationErrors) {
1600
+ const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1601
+ return {
1602
+ path: moduleId,
1603
+ schema,
1604
+ source,
1605
+ errors: revalidated && {
1606
+ validation: revalidated
1607
+ }
1608
+ };
1609
+ }
1565
1610
  }
1566
1611
  return {
1567
1612
  path: moduleId,
@@ -1576,7 +1621,7 @@ class ValServer {
1576
1621
  // The reason is that validate will be called inside QuickJS (in the future, hopefully),
1577
1622
  // which does not have access to the filesystem, at least not at the time of writing this comment.
1578
1623
  // If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
1579
- async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders) {
1624
+ async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
1580
1625
  const revalidatedValidationErrors = {};
1581
1626
  for (const pathStr in validationErrors) {
1582
1627
  const errorSourcePath = pathStr;
@@ -1598,7 +1643,7 @@ class ValServer {
1598
1643
  if (updatedFileMetadata) {
1599
1644
  const fileRes = await this.getFiles(fileRef, {
1600
1645
  sha256: updatedFileMetadata.sha256
1601
- }, cookies, requestHeaders);
1646
+ }, cookies, reqHeaders);
1602
1647
  if (fileRes.status === 200 && fileRes.body) {
1603
1648
  const res = new Response(fileRes.body);
1604
1649
  fileBuffer = Buffer.from(await res.arrayBuffer());
@@ -1608,14 +1653,23 @@ class ValServer {
1608
1653
  });
1609
1654
  }
1610
1655
  }
1611
- if (!fileBuffer) {
1612
- try {
1613
- fileBuffer = await this.readStaticBinaryFile(filePath);
1614
- } catch (err) {
1615
- console.error("Val: unexpected error while reading image / file:", filePath, {
1616
- error: err
1656
+ // try fetch file directly via http
1657
+ if (fileRef.startsWith("/public")) {
1658
+ const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
1659
+ const fileUrl = fileRef.slice("/public".length);
1660
+ const fetchRes = await fetch(new URL(fileUrl, host));
1661
+ if (fetchRes.status === 200) {
1662
+ fileBuffer = Buffer.from(await fetchRes.arrayBuffer());
1663
+ } else {
1664
+ console.error("Val: unexpected error while fetching image / file:", fileRef, {
1665
+ error: {
1666
+ status: fetchRes.status,
1667
+ url: fetchRes.url
1668
+ }
1617
1669
  });
1618
1670
  }
1671
+ } else {
1672
+ console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
1619
1673
  }
1620
1674
  if (!fileBuffer) {
1621
1675
  revalidatedValidationErrors[errorSourcePath].push({
@@ -1762,7 +1816,7 @@ class ValServer {
1762
1816
  const moduleId = moduleIdStr;
1763
1817
  const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
1764
1818
  // TODO: refine to ModuleId and PatchId when parsing
1765
- patchIdsByModuleId, patchesById, fileUpdates, true, cookies, requestHeaders);
1819
+ patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
1766
1820
  if (serializedModuleContent.errors) {
1767
1821
  validationErrorsByModuleId[moduleId] = serializedModuleContent;
1768
1822
  }
@@ -2266,8 +2320,8 @@ class LocalValServer extends ValServer {
2266
2320
  // No RemoteFS so nothing to ensure
2267
2321
  return fp.result.ok(undefined);
2268
2322
  }
2269
- getModule(moduleId) {
2270
- return this.options.service.get(moduleId);
2323
+ getModule(moduleId, options) {
2324
+ return this.options.service.get(moduleId, "", options);
2271
2325
  }
2272
2326
  async execCommit(patches) {
2273
2327
  for (const [patchId, moduleId, patch] of patches) {
@@ -2555,11 +2609,11 @@ class ProxyValServer extends ValServer {
2555
2609
 
2556
2610
  /** Remote FS dependent methods: */
2557
2611
 
2558
- async getModule(moduleId) {
2612
+ async getModule(moduleId, options) {
2559
2613
  if (!this.lazyService) {
2560
2614
  this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
2561
2615
  }
2562
- return this.lazyService.get(moduleId);
2616
+ return this.lazyService.get(moduleId, "", options);
2563
2617
  }
2564
2618
  execCommit(patches, cookies) {
2565
2619
  return withAuth(this.options.valSecret, cookies, "execCommit", async ({
@@ -3421,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
3421
3475
  return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
3422
3476
  patch: url.searchParams.get("patch") || undefined,
3423
3477
  schema: url.searchParams.get("schema") || undefined,
3424
- source: url.searchParams.get("source") || undefined
3478
+ source: url.searchParams.get("source") || undefined,
3479
+ validate: url.searchParams.get("validate") || undefined
3425
3480
  }, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
3426
3481
  } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
3427
3482
  return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
@@ -7,6 +7,7 @@ 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
+ import { VAL_CSS_PATH, VAL_APP_ID, VAL_OVERLAY_ID } from '@valbuild/ui';
10
11
  import z, { z as z$1 } from 'zod';
11
12
  import { MIME_TYPES_TO_EXT, filenameToMimeType, VAL_ENABLE_COOKIE_NAME, VAL_STATE_COOKIE as VAL_STATE_COOKIE$1, VAL_SESSION_COOKIE as VAL_SESSION_COOKIE$1 } from '@valbuild/shared/internal';
12
13
  import sizeOf from 'image-size';
@@ -661,7 +662,7 @@ const patchSourceFile = (sourceFile, patch) => {
661
662
  return applyPatch(sourceFile, ops$1, patch);
662
663
  };
663
664
 
664
- const readValFile = async (id, valConfigPath, runtime) => {
665
+ const readValFile = async (id, valConfigPath, runtime, options) => {
665
666
  const context = runtime.newContext();
666
667
 
667
668
  // avoid failures when console.log is called
@@ -679,8 +680,22 @@ const readValFile = async (id, valConfigPath, runtime) => {
679
680
  const processHandle = context.newObject();
680
681
  context.setProp(processHandle, "env", envHandle);
681
682
  context.setProp(context.global, "process", processHandle);
683
+ const optionsHandle = context.newObject();
684
+ if (options) {
685
+ if (options.validate !== undefined) {
686
+ context.setProp(optionsHandle, "validate", context.newNumber(+options.validate));
687
+ }
688
+ if (options.source !== undefined) {
689
+ context.setProp(optionsHandle, "source", context.newNumber(+options.source));
690
+ }
691
+ if (options.schema !== undefined) {
692
+ context.setProp(optionsHandle, "schema", context.newNumber(+options.schema));
693
+ }
694
+ }
695
+ context.setProp(context.global, "__VAL_OPTIONS__", optionsHandle);
682
696
  envHandle.dispose();
683
697
  processHandle.dispose();
698
+ optionsHandle.dispose();
684
699
  try {
685
700
  const modulePath = `.${id}.val`;
686
701
  const code = `import * as valModule from ${JSON.stringify(modulePath)};
@@ -688,12 +703,12 @@ import { Internal } from "@valbuild/core";
688
703
 
689
704
  globalThis.valModule = {
690
705
  id: valModule?.default && Internal.getValPath(valModule?.default),
691
- schema: valModule?.default && Internal.getSchema(valModule?.default)?.serialize(),
692
- source: valModule?.default && Internal.getSource(valModule?.default),
693
- validation: valModule?.default && Internal.getSchema(valModule?.default)?.validate(
706
+ schema: !!globalThis['__VAL_OPTIONS__'].schema ? valModule?.default && Internal.getSchema(valModule?.default)?.serialize() : undefined,
707
+ source: !!globalThis['__VAL_OPTIONS__'].source ? valModule?.default && Internal.getSource(valModule?.default) : undefined,
708
+ validation: !!globalThis['__VAL_OPTIONS__'].validate ? valModule?.default && Internal.getSchema(valModule?.default)?.validate(
694
709
  valModule?.default && Internal.getValPath(valModule?.default) || "/",
695
710
  valModule?.default && Internal.getSource(valModule?.default)
696
- ),
711
+ ) : undefined,
697
712
  defaultExport: !!valModule?.default,
698
713
  };
699
714
  `;
@@ -724,9 +739,9 @@ globalThis.valModule = {
724
739
  fatalErrors.push(`Wrong c.define id! Expected: '${id}', found: '${valModule.id}'`);
725
740
  } else if (encodeURIComponent(valModule.id).replace(/%2F/g, "/") !== valModule.id) {
726
741
  fatalErrors.push(`Invalid c.define id! Must be a web-safe path without escape characters, found: '${valModule.id}', which was encoded as: '${encodeURIComponent(valModule.id).replace("%2F", "/")}'`);
727
- } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined) {
742
+ } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.schema) === undefined && options.schema) {
728
743
  fatalErrors.push(`Expected val id: '${id}' to have a schema`);
729
- } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined) {
744
+ } else if ((valModule === null || valModule === void 0 ? void 0 : valModule.source) === undefined && options.source) {
730
745
  fatalErrors.push(`Expected val id: '${id}' to have a source`);
731
746
  }
732
747
  }
@@ -967,7 +982,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
967
982
  }
968
983
  if (modulePath === "@valbuild/react/internal") {
969
984
  return {
970
- value: "export const useVal = () => { throw Error(`Cannot use 'useVal' in this type of file`) }; export function ValProvider() { throw Error(`Cannot use 'ValProvider' in this type of file`) }; export function ValRichText() { throw Error(`Cannot use 'ValRichText' in this type of file`)};"
985
+ value: `
986
+ const useVal = () => { throw Error('Cannot use \\'useVal\\' in this type of file') };
987
+ export function ValProvider() { throw Error('Cannot use \\'ValProvider\\' in this type of file') };
988
+ export function ValRichText() { throw Error('Cannot use \\'ValRichText\\' in this type of file')};`
971
989
  };
972
990
  }
973
991
  if (modulePath === "@valbuild/ui") {
@@ -976,10 +994,10 @@ async function newValQuickJSRuntime(quickJSModule, moduleLoader, {
976
994
  export const ValOverlay = () => {
977
995
  throw Error("Cannot use 'ValOverlay' in this type of file")
978
996
  };
979
- export const VAL_CSS_PATH = "/spa/index.css";
980
- export const VAL_APP_PATH = "/app";
981
- export const VAL_APP_ID = "val-app";
982
- export const VAL_OVERLAY_ID = "val-overlay";
997
+ export const VAL_CSS_PATH = "${VAL_CSS_PATH}";
998
+ export const VAL_APP_PATH = "${VAL_CSS_PATH}";
999
+ export const VAL_APP_ID = "${VAL_APP_ID}";
1000
+ export const VAL_OVERLAY_ID = "${VAL_OVERLAY_ID}";
983
1001
  export const IS_DEV = false;
984
1002
  `
985
1003
  };
@@ -1006,7 +1024,21 @@ export const IS_DEV = false;
1006
1024
  }
1007
1025
  if (modulePath.startsWith("react")) {
1008
1026
  return {
1009
- value: "export const createContext = () => new Proxy({}, { get() { return () => { throw new Error(`Cannot use 'createContext' in this file`) } } } ); export const useTransition = () => { throw Error(`Cannot use 'useTransition' in this type of file`) }; export default new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'react' in this file`) } } } )"
1027
+ value: `
1028
+ export const createContext = () => new Proxy({}, { get() { return () => { throw new Error('Cannot use \\'createContext\\' in this file') } } } );
1029
+ export const useTransition = () => { throw Error('Cannot use \\'useTransition\\' in this type of file') };
1030
+
1031
+ export default new Proxy({}, {
1032
+ get(target, props) {
1033
+ // React.createContext might be called on top-level
1034
+ if (props === 'createContext') {
1035
+ return createContext;
1036
+ }
1037
+ return () => {
1038
+ throw new Error('Cannot import \\'react\\' in this file');
1039
+ }
1040
+ }
1041
+ })`
1010
1042
  };
1011
1043
  }
1012
1044
  if (modulePath.includes("/ValNextProvider")) {
@@ -1137,8 +1169,12 @@ class Service {
1137
1169
  this.runtime = runtime;
1138
1170
  this.valConfigPath = valConfigPath || "./val.config";
1139
1171
  }
1140
- async get(moduleId, modulePath = "") {
1141
- const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime);
1172
+ async get(moduleId, modulePath, options) {
1173
+ const valModule = await readValFile(moduleId, this.valConfigPath, this.runtime, options ?? {
1174
+ validate: true,
1175
+ source: true,
1176
+ schema: true
1177
+ });
1142
1178
  if (valModule.source && valModule.schema) {
1143
1179
  const resolved = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
1144
1180
  const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
@@ -1403,8 +1439,11 @@ class ValServer {
1403
1439
  if (result.isErr(ensureRes)) {
1404
1440
  return ensureRes.error;
1405
1441
  }
1406
- const moduleIds = this.getAllModules(treePath);
1407
1442
  const applyPatches = query.patch === "true";
1443
+ const execValidations = query.validate === "true";
1444
+ const includeSource = query.source === "true";
1445
+ const includeSchema = query.schema === "true";
1446
+ const moduleIds = this.getAllModules(treePath);
1408
1447
  let {
1409
1448
  patchIdsByModuleId,
1410
1449
  patchesById,
@@ -1424,7 +1463,7 @@ class ValServer {
1424
1463
  fileUpdates = res.value.fileUpdates;
1425
1464
  }
1426
1465
  const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
1427
- return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders);
1466
+ return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
1428
1467
  }));
1429
1468
  const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
1430
1469
  const module = {
@@ -1478,13 +1517,17 @@ class ValServer {
1478
1517
 
1479
1518
  /* */
1480
1519
 
1481
- async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders) {
1482
- const serializedModuleContent = await this.getModule(moduleId);
1483
- const schema = serializedModuleContent.schema;
1484
- const maybeSource = serializedModuleContent.source;
1520
+ async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, validate, includeSource, includeSchema) {
1521
+ const serializedModuleContent = await this.getModule(moduleId, {
1522
+ validate: validate,
1523
+ source: includeSource,
1524
+ schema: includeSchema
1525
+ });
1485
1526
  if (!applyPatches) {
1486
1527
  return serializedModuleContent;
1487
1528
  }
1529
+ const schema = serializedModuleContent.schema;
1530
+ const maybeSource = serializedModuleContent.source;
1488
1531
  if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
1489
1532
  return serializedModuleContent;
1490
1533
  }
@@ -1520,17 +1563,19 @@ class ValServer {
1520
1563
  };
1521
1564
  }
1522
1565
  }
1523
- const validationErrors = deserializeSchema(schema).validate(moduleId, source);
1524
- if (validationErrors) {
1525
- const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1526
- return {
1527
- path: moduleId,
1528
- schema,
1529
- source,
1530
- errors: revalidated && {
1531
- validation: revalidated
1532
- }
1533
- };
1566
+ if (validate) {
1567
+ const validationErrors = deserializeSchema(schema).validate(moduleId, source);
1568
+ if (validationErrors) {
1569
+ const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1570
+ return {
1571
+ path: moduleId,
1572
+ schema,
1573
+ source,
1574
+ errors: revalidated && {
1575
+ validation: revalidated
1576
+ }
1577
+ };
1578
+ }
1534
1579
  }
1535
1580
  return {
1536
1581
  path: moduleId,
@@ -1545,7 +1590,7 @@ class ValServer {
1545
1590
  // The reason is that validate will be called inside QuickJS (in the future, hopefully),
1546
1591
  // which does not have access to the filesystem, at least not at the time of writing this comment.
1547
1592
  // If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
1548
- async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders) {
1593
+ async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
1549
1594
  const revalidatedValidationErrors = {};
1550
1595
  for (const pathStr in validationErrors) {
1551
1596
  const errorSourcePath = pathStr;
@@ -1567,7 +1612,7 @@ class ValServer {
1567
1612
  if (updatedFileMetadata) {
1568
1613
  const fileRes = await this.getFiles(fileRef, {
1569
1614
  sha256: updatedFileMetadata.sha256
1570
- }, cookies, requestHeaders);
1615
+ }, cookies, reqHeaders);
1571
1616
  if (fileRes.status === 200 && fileRes.body) {
1572
1617
  const res = new Response(fileRes.body);
1573
1618
  fileBuffer = Buffer.from(await res.arrayBuffer());
@@ -1577,14 +1622,23 @@ class ValServer {
1577
1622
  });
1578
1623
  }
1579
1624
  }
1580
- if (!fileBuffer) {
1581
- try {
1582
- fileBuffer = await this.readStaticBinaryFile(filePath);
1583
- } catch (err) {
1584
- console.error("Val: unexpected error while reading image / file:", filePath, {
1585
- error: err
1625
+ // try fetch file directly via http
1626
+ if (fileRef.startsWith("/public")) {
1627
+ const host = `${reqHeaders["x-forwarded-proto"]}://${reqHeaders["host"]}`;
1628
+ const fileUrl = fileRef.slice("/public".length);
1629
+ const fetchRes = await fetch(new URL(fileUrl, host));
1630
+ if (fetchRes.status === 200) {
1631
+ fileBuffer = Buffer.from(await fetchRes.arrayBuffer());
1632
+ } else {
1633
+ console.error("Val: unexpected error while fetching image / file:", fileRef, {
1634
+ error: {
1635
+ status: fetchRes.status,
1636
+ url: fetchRes.url
1637
+ }
1586
1638
  });
1587
1639
  }
1640
+ } else {
1641
+ console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
1588
1642
  }
1589
1643
  if (!fileBuffer) {
1590
1644
  revalidatedValidationErrors[errorSourcePath].push({
@@ -1731,7 +1785,7 @@ class ValServer {
1731
1785
  const moduleId = moduleIdStr;
1732
1786
  const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
1733
1787
  // TODO: refine to ModuleId and PatchId when parsing
1734
- patchIdsByModuleId, patchesById, fileUpdates, true, cookies, requestHeaders);
1788
+ patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
1735
1789
  if (serializedModuleContent.errors) {
1736
1790
  validationErrorsByModuleId[moduleId] = serializedModuleContent;
1737
1791
  }
@@ -2235,8 +2289,8 @@ class LocalValServer extends ValServer {
2235
2289
  // No RemoteFS so nothing to ensure
2236
2290
  return result.ok(undefined);
2237
2291
  }
2238
- getModule(moduleId) {
2239
- return this.options.service.get(moduleId);
2292
+ getModule(moduleId, options) {
2293
+ return this.options.service.get(moduleId, "", options);
2240
2294
  }
2241
2295
  async execCommit(patches) {
2242
2296
  for (const [patchId, moduleId, patch] of patches) {
@@ -2524,11 +2578,11 @@ class ProxyValServer extends ValServer {
2524
2578
 
2525
2579
  /** Remote FS dependent methods: */
2526
2580
 
2527
- async getModule(moduleId) {
2581
+ async getModule(moduleId, options) {
2528
2582
  if (!this.lazyService) {
2529
2583
  this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
2530
2584
  }
2531
- return this.lazyService.get(moduleId);
2585
+ return this.lazyService.get(moduleId, "", options);
2532
2586
  }
2533
2587
  execCommit(patches, cookies) {
2534
2588
  return withAuth(this.options.valSecret, cookies, "execCommit", async ({
@@ -3390,7 +3444,8 @@ function createValApiRouter(route, valServerPromise, convert) {
3390
3444
  return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
3391
3445
  patch: url.searchParams.get("patch") || undefined,
3392
3446
  schema: url.searchParams.get("schema") || undefined,
3393
- source: url.searchParams.get("source") || undefined
3447
+ source: url.searchParams.get("source") || undefined,
3448
+ validate: url.searchParams.get("validate") || undefined
3394
3449
  }, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
3395
3450
  } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
3396
3451
  return withTreePath(path, PATCHES_PATH_PREFIX)(async () => convert(await valServer.getPatches({
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "./package.json": "./package.json"
13
13
  },
14
14
  "types": "dist/valbuild-server.cjs.d.ts",
15
- "version": "0.60.18",
15
+ "version": "0.60.19",
16
16
  "scripts": {
17
17
  "typecheck": "tsc --noEmit",
18
18
  "test": "jest",
@@ -24,9 +24,9 @@
24
24
  "concurrently": "^7.6.0"
25
25
  },
26
26
  "dependencies": {
27
- "@valbuild/core": "~0.60.18",
28
- "@valbuild/shared": "~0.60.18",
29
- "@valbuild/ui": "~0.60.18",
27
+ "@valbuild/core": "~0.60.19",
28
+ "@valbuild/shared": "~0.60.19",
29
+ "@valbuild/ui": "~0.60.19",
30
30
  "express": "^4.18.2",
31
31
  "image-size": "^1.0.2",
32
32
  "minimatch": "^3.0.4",