@valbuild/server 0.60.17 → 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 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")) {
@@ -1045,6 +1077,11 @@ export const IS_DEV = false;
1045
1077
  value: "export const ValNextProvider = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValNextProvider' in this file`) } } } )"
1046
1078
  };
1047
1079
  }
1080
+ if (modulePath.includes("/ValContext")) {
1081
+ return {
1082
+ value: "export const useValEvents = () => { throw Error(`Cannot use 'useValEvents' in this type of file`) }; export const ValContext = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValContext' in this file`) } } } ) export const ValEvents = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValEvents' in this file`) } } } )"
1083
+ };
1084
+ }
1048
1085
  if (modulePath.includes("/ValImage")) {
1049
1086
  return {
1050
1087
  value: "export const ValImage = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValImage' in this file`) } } } )"
@@ -1110,6 +1147,11 @@ export const IS_DEV = false;
1110
1147
  value: requestedName
1111
1148
  };
1112
1149
  }
1150
+ if (requestedName.includes("/ValContext")) {
1151
+ return {
1152
+ value: requestedName
1153
+ };
1154
+ }
1113
1155
  if (requestedName.includes("/ValImage")) {
1114
1156
  return {
1115
1157
  value: requestedName
@@ -1158,8 +1200,12 @@ class Service {
1158
1200
  this.runtime = runtime;
1159
1201
  this.valConfigPath = valConfigPath || "./val.config";
1160
1202
  }
1161
- async get(moduleId, modulePath = "") {
1162
- 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
+ });
1163
1209
  if (valModule.source && valModule.schema) {
1164
1210
  const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
1165
1211
  const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
@@ -1424,8 +1470,11 @@ class ValServer {
1424
1470
  if (fp.result.isErr(ensureRes)) {
1425
1471
  return ensureRes.error;
1426
1472
  }
1427
- const moduleIds = this.getAllModules(treePath);
1428
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);
1429
1478
  let {
1430
1479
  patchIdsByModuleId,
1431
1480
  patchesById,
@@ -1445,7 +1494,7 @@ class ValServer {
1445
1494
  fileUpdates = res.value.fileUpdates;
1446
1495
  }
1447
1496
  const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
1448
- return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders);
1497
+ return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
1449
1498
  }));
1450
1499
  const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
1451
1500
  const module = {
@@ -1499,13 +1548,17 @@ class ValServer {
1499
1548
 
1500
1549
  /* */
1501
1550
 
1502
- async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders) {
1503
- const serializedModuleContent = await this.getModule(moduleId);
1504
- const schema = serializedModuleContent.schema;
1505
- 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
+ });
1506
1557
  if (!applyPatches) {
1507
1558
  return serializedModuleContent;
1508
1559
  }
1560
+ const schema = serializedModuleContent.schema;
1561
+ const maybeSource = serializedModuleContent.source;
1509
1562
  if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
1510
1563
  return serializedModuleContent;
1511
1564
  }
@@ -1541,17 +1594,19 @@ class ValServer {
1541
1594
  };
1542
1595
  }
1543
1596
  }
1544
- const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
1545
- if (validationErrors) {
1546
- const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1547
- return {
1548
- path: moduleId,
1549
- schema,
1550
- source,
1551
- errors: revalidated && {
1552
- validation: revalidated
1553
- }
1554
- };
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
+ }
1555
1610
  }
1556
1611
  return {
1557
1612
  path: moduleId,
@@ -1566,7 +1621,7 @@ class ValServer {
1566
1621
  // The reason is that validate will be called inside QuickJS (in the future, hopefully),
1567
1622
  // which does not have access to the filesystem, at least not at the time of writing this comment.
1568
1623
  // If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
1569
- async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders) {
1624
+ async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
1570
1625
  const revalidatedValidationErrors = {};
1571
1626
  for (const pathStr in validationErrors) {
1572
1627
  const errorSourcePath = pathStr;
@@ -1588,7 +1643,7 @@ class ValServer {
1588
1643
  if (updatedFileMetadata) {
1589
1644
  const fileRes = await this.getFiles(fileRef, {
1590
1645
  sha256: updatedFileMetadata.sha256
1591
- }, cookies, requestHeaders);
1646
+ }, cookies, reqHeaders);
1592
1647
  if (fileRes.status === 200 && fileRes.body) {
1593
1648
  const res = new Response(fileRes.body);
1594
1649
  fileBuffer = Buffer.from(await res.arrayBuffer());
@@ -1598,14 +1653,23 @@ class ValServer {
1598
1653
  });
1599
1654
  }
1600
1655
  }
1601
- if (!fileBuffer) {
1602
- try {
1603
- fileBuffer = await this.readStaticBinaryFile(filePath);
1604
- } catch (err) {
1605
- console.error("Val: unexpected error while reading image / file:", filePath, {
1606
- 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
+ }
1607
1669
  });
1608
1670
  }
1671
+ } else {
1672
+ console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
1609
1673
  }
1610
1674
  if (!fileBuffer) {
1611
1675
  revalidatedValidationErrors[errorSourcePath].push({
@@ -1752,7 +1816,7 @@ class ValServer {
1752
1816
  const moduleId = moduleIdStr;
1753
1817
  const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
1754
1818
  // TODO: refine to ModuleId and PatchId when parsing
1755
- patchIdsByModuleId, patchesById, fileUpdates, true, cookies, requestHeaders);
1819
+ patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
1756
1820
  if (serializedModuleContent.errors) {
1757
1821
  validationErrorsByModuleId[moduleId] = serializedModuleContent;
1758
1822
  }
@@ -2256,8 +2320,8 @@ class LocalValServer extends ValServer {
2256
2320
  // No RemoteFS so nothing to ensure
2257
2321
  return fp.result.ok(undefined);
2258
2322
  }
2259
- getModule(moduleId) {
2260
- return this.options.service.get(moduleId);
2323
+ getModule(moduleId, options) {
2324
+ return this.options.service.get(moduleId, "", options);
2261
2325
  }
2262
2326
  async execCommit(patches) {
2263
2327
  for (const [patchId, moduleId, patch] of patches) {
@@ -2545,11 +2609,11 @@ class ProxyValServer extends ValServer {
2545
2609
 
2546
2610
  /** Remote FS dependent methods: */
2547
2611
 
2548
- async getModule(moduleId) {
2612
+ async getModule(moduleId, options) {
2549
2613
  if (!this.lazyService) {
2550
2614
  this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
2551
2615
  }
2552
- return this.lazyService.get(moduleId);
2616
+ return this.lazyService.get(moduleId, "", options);
2553
2617
  }
2554
2618
  execCommit(patches, cookies) {
2555
2619
  return withAuth(this.options.valSecret, cookies, "execCommit", async ({
@@ -3411,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
3411
3475
  return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
3412
3476
  patch: url.searchParams.get("patch") || undefined,
3413
3477
  schema: url.searchParams.get("schema") || undefined,
3414
- source: url.searchParams.get("source") || undefined
3478
+ source: url.searchParams.get("source") || undefined,
3479
+ validate: url.searchParams.get("validate") || undefined
3415
3480
  }, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
3416
3481
  } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
3417
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 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")) {
@@ -1045,6 +1077,11 @@ export const IS_DEV = false;
1045
1077
  value: "export const ValNextProvider = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValNextProvider' in this file`) } } } )"
1046
1078
  };
1047
1079
  }
1080
+ if (modulePath.includes("/ValContext")) {
1081
+ return {
1082
+ value: "export const useValEvents = () => { throw Error(`Cannot use 'useValEvents' in this type of file`) }; export const ValContext = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValContext' in this file`) } } } ) export const ValEvents = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValEvents' in this file`) } } } )"
1083
+ };
1084
+ }
1048
1085
  if (modulePath.includes("/ValImage")) {
1049
1086
  return {
1050
1087
  value: "export const ValImage = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValImage' in this file`) } } } )"
@@ -1110,6 +1147,11 @@ export const IS_DEV = false;
1110
1147
  value: requestedName
1111
1148
  };
1112
1149
  }
1150
+ if (requestedName.includes("/ValContext")) {
1151
+ return {
1152
+ value: requestedName
1153
+ };
1154
+ }
1113
1155
  if (requestedName.includes("/ValImage")) {
1114
1156
  return {
1115
1157
  value: requestedName
@@ -1158,8 +1200,12 @@ class Service {
1158
1200
  this.runtime = runtime;
1159
1201
  this.valConfigPath = valConfigPath || "./val.config";
1160
1202
  }
1161
- async get(moduleId, modulePath = "") {
1162
- 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
+ });
1163
1209
  if (valModule.source && valModule.schema) {
1164
1210
  const resolved = core.Internal.resolvePath(modulePath, valModule.source, valModule.schema);
1165
1211
  const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
@@ -1424,8 +1470,11 @@ class ValServer {
1424
1470
  if (fp.result.isErr(ensureRes)) {
1425
1471
  return ensureRes.error;
1426
1472
  }
1427
- const moduleIds = this.getAllModules(treePath);
1428
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);
1429
1478
  let {
1430
1479
  patchIdsByModuleId,
1431
1480
  patchesById,
@@ -1445,7 +1494,7 @@ class ValServer {
1445
1494
  fileUpdates = res.value.fileUpdates;
1446
1495
  }
1447
1496
  const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
1448
- return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders);
1497
+ return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
1449
1498
  }));
1450
1499
  const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
1451
1500
  const module = {
@@ -1499,13 +1548,17 @@ class ValServer {
1499
1548
 
1500
1549
  /* */
1501
1550
 
1502
- async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders) {
1503
- const serializedModuleContent = await this.getModule(moduleId);
1504
- const schema = serializedModuleContent.schema;
1505
- 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
+ });
1506
1557
  if (!applyPatches) {
1507
1558
  return serializedModuleContent;
1508
1559
  }
1560
+ const schema = serializedModuleContent.schema;
1561
+ const maybeSource = serializedModuleContent.source;
1509
1562
  if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
1510
1563
  return serializedModuleContent;
1511
1564
  }
@@ -1541,17 +1594,19 @@ class ValServer {
1541
1594
  };
1542
1595
  }
1543
1596
  }
1544
- const validationErrors = core.deserializeSchema(schema).validate(moduleId, source);
1545
- if (validationErrors) {
1546
- const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1547
- return {
1548
- path: moduleId,
1549
- schema,
1550
- source,
1551
- errors: revalidated && {
1552
- validation: revalidated
1553
- }
1554
- };
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
+ }
1555
1610
  }
1556
1611
  return {
1557
1612
  path: moduleId,
@@ -1566,7 +1621,7 @@ class ValServer {
1566
1621
  // The reason is that validate will be called inside QuickJS (in the future, hopefully),
1567
1622
  // which does not have access to the filesystem, at least not at the time of writing this comment.
1568
1623
  // If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
1569
- async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders) {
1624
+ async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
1570
1625
  const revalidatedValidationErrors = {};
1571
1626
  for (const pathStr in validationErrors) {
1572
1627
  const errorSourcePath = pathStr;
@@ -1588,7 +1643,7 @@ class ValServer {
1588
1643
  if (updatedFileMetadata) {
1589
1644
  const fileRes = await this.getFiles(fileRef, {
1590
1645
  sha256: updatedFileMetadata.sha256
1591
- }, cookies, requestHeaders);
1646
+ }, cookies, reqHeaders);
1592
1647
  if (fileRes.status === 200 && fileRes.body) {
1593
1648
  const res = new Response(fileRes.body);
1594
1649
  fileBuffer = Buffer.from(await res.arrayBuffer());
@@ -1598,14 +1653,23 @@ class ValServer {
1598
1653
  });
1599
1654
  }
1600
1655
  }
1601
- if (!fileBuffer) {
1602
- try {
1603
- fileBuffer = await this.readStaticBinaryFile(filePath);
1604
- } catch (err) {
1605
- console.error("Val: unexpected error while reading image / file:", filePath, {
1606
- 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
+ }
1607
1669
  });
1608
1670
  }
1671
+ } else {
1672
+ console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
1609
1673
  }
1610
1674
  if (!fileBuffer) {
1611
1675
  revalidatedValidationErrors[errorSourcePath].push({
@@ -1752,7 +1816,7 @@ class ValServer {
1752
1816
  const moduleId = moduleIdStr;
1753
1817
  const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
1754
1818
  // TODO: refine to ModuleId and PatchId when parsing
1755
- patchIdsByModuleId, patchesById, fileUpdates, true, cookies, requestHeaders);
1819
+ patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
1756
1820
  if (serializedModuleContent.errors) {
1757
1821
  validationErrorsByModuleId[moduleId] = serializedModuleContent;
1758
1822
  }
@@ -2256,8 +2320,8 @@ class LocalValServer extends ValServer {
2256
2320
  // No RemoteFS so nothing to ensure
2257
2321
  return fp.result.ok(undefined);
2258
2322
  }
2259
- getModule(moduleId) {
2260
- return this.options.service.get(moduleId);
2323
+ getModule(moduleId, options) {
2324
+ return this.options.service.get(moduleId, "", options);
2261
2325
  }
2262
2326
  async execCommit(patches) {
2263
2327
  for (const [patchId, moduleId, patch] of patches) {
@@ -2545,11 +2609,11 @@ class ProxyValServer extends ValServer {
2545
2609
 
2546
2610
  /** Remote FS dependent methods: */
2547
2611
 
2548
- async getModule(moduleId) {
2612
+ async getModule(moduleId, options) {
2549
2613
  if (!this.lazyService) {
2550
2614
  this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
2551
2615
  }
2552
- return this.lazyService.get(moduleId);
2616
+ return this.lazyService.get(moduleId, "", options);
2553
2617
  }
2554
2618
  execCommit(patches, cookies) {
2555
2619
  return withAuth(this.options.valSecret, cookies, "execCommit", async ({
@@ -3411,7 +3475,8 @@ function createValApiRouter(route, valServerPromise, convert) {
3411
3475
  return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
3412
3476
  patch: url.searchParams.get("patch") || undefined,
3413
3477
  schema: url.searchParams.get("schema") || undefined,
3414
- source: url.searchParams.get("source") || undefined
3478
+ source: url.searchParams.get("source") || undefined,
3479
+ validate: url.searchParams.get("validate") || undefined
3415
3480
  }, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
3416
3481
  } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
3417
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 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")) {
@@ -1014,6 +1046,11 @@ export const IS_DEV = false;
1014
1046
  value: "export const ValNextProvider = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValNextProvider' in this file`) } } } )"
1015
1047
  };
1016
1048
  }
1049
+ if (modulePath.includes("/ValContext")) {
1050
+ return {
1051
+ value: "export const useValEvents = () => { throw Error(`Cannot use 'useValEvents' in this type of file`) }; export const ValContext = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValContext' in this file`) } } } ) export const ValEvents = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValEvents' in this file`) } } } )"
1052
+ };
1053
+ }
1017
1054
  if (modulePath.includes("/ValImage")) {
1018
1055
  return {
1019
1056
  value: "export const ValImage = new Proxy({}, { get() { return () => { throw new Error(`Cannot import 'ValImage' in this file`) } } } )"
@@ -1079,6 +1116,11 @@ export const IS_DEV = false;
1079
1116
  value: requestedName
1080
1117
  };
1081
1118
  }
1119
+ if (requestedName.includes("/ValContext")) {
1120
+ return {
1121
+ value: requestedName
1122
+ };
1123
+ }
1082
1124
  if (requestedName.includes("/ValImage")) {
1083
1125
  return {
1084
1126
  value: requestedName
@@ -1127,8 +1169,12 @@ class Service {
1127
1169
  this.runtime = runtime;
1128
1170
  this.valConfigPath = valConfigPath || "./val.config";
1129
1171
  }
1130
- async get(moduleId, modulePath = "") {
1131
- 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
+ });
1132
1178
  if (valModule.source && valModule.schema) {
1133
1179
  const resolved = Internal.resolvePath(modulePath, valModule.source, valModule.schema);
1134
1180
  const sourcePath = resolved.path ? [moduleId, resolved.path].join(".") : moduleId;
@@ -1393,8 +1439,11 @@ class ValServer {
1393
1439
  if (result.isErr(ensureRes)) {
1394
1440
  return ensureRes.error;
1395
1441
  }
1396
- const moduleIds = this.getAllModules(treePath);
1397
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);
1398
1447
  let {
1399
1448
  patchIdsByModuleId,
1400
1449
  patchesById,
@@ -1414,7 +1463,7 @@ class ValServer {
1414
1463
  fileUpdates = res.value.fileUpdates;
1415
1464
  }
1416
1465
  const possiblyPatchedContent = await Promise.all(moduleIds.map(async moduleId => {
1417
- return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders);
1466
+ return this.applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, applyPatches, execValidations, includeSource, includeSchema);
1418
1467
  }));
1419
1468
  const modules = Object.fromEntries(possiblyPatchedContent.map(serializedModuleContent => {
1420
1469
  const module = {
@@ -1468,13 +1517,17 @@ class ValServer {
1468
1517
 
1469
1518
  /* */
1470
1519
 
1471
- async applyAllPatchesThenValidate(moduleId, patchIdsByModuleId, patchesById, fileUpdates, applyPatches, cookies, requestHeaders) {
1472
- const serializedModuleContent = await this.getModule(moduleId);
1473
- const schema = serializedModuleContent.schema;
1474
- 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
+ });
1475
1526
  if (!applyPatches) {
1476
1527
  return serializedModuleContent;
1477
1528
  }
1529
+ const schema = serializedModuleContent.schema;
1530
+ const maybeSource = serializedModuleContent.source;
1478
1531
  if (serializedModuleContent.errors && (serializedModuleContent.errors.fatal || serializedModuleContent.errors.invalidModuleId)) {
1479
1532
  return serializedModuleContent;
1480
1533
  }
@@ -1510,17 +1563,19 @@ class ValServer {
1510
1563
  };
1511
1564
  }
1512
1565
  }
1513
- const validationErrors = deserializeSchema(schema).validate(moduleId, source);
1514
- if (validationErrors) {
1515
- const revalidated = await this.revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders);
1516
- return {
1517
- path: moduleId,
1518
- schema,
1519
- source,
1520
- errors: revalidated && {
1521
- validation: revalidated
1522
- }
1523
- };
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
+ }
1524
1579
  }
1525
1580
  return {
1526
1581
  path: moduleId,
@@ -1535,7 +1590,7 @@ class ValServer {
1535
1590
  // The reason is that validate will be called inside QuickJS (in the future, hopefully),
1536
1591
  // which does not have access to the filesystem, at least not at the time of writing this comment.
1537
1592
  // If you are reading this, and we still are not using QuickJS to validate, this assumption might be wrong.
1538
- async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, requestHeaders) {
1593
+ async revalidateImageAndFileValidation(validationErrors, fileUpdates, cookies, reqHeaders) {
1539
1594
  const revalidatedValidationErrors = {};
1540
1595
  for (const pathStr in validationErrors) {
1541
1596
  const errorSourcePath = pathStr;
@@ -1557,7 +1612,7 @@ class ValServer {
1557
1612
  if (updatedFileMetadata) {
1558
1613
  const fileRes = await this.getFiles(fileRef, {
1559
1614
  sha256: updatedFileMetadata.sha256
1560
- }, cookies, requestHeaders);
1615
+ }, cookies, reqHeaders);
1561
1616
  if (fileRes.status === 200 && fileRes.body) {
1562
1617
  const res = new Response(fileRes.body);
1563
1618
  fileBuffer = Buffer.from(await res.arrayBuffer());
@@ -1567,14 +1622,23 @@ class ValServer {
1567
1622
  });
1568
1623
  }
1569
1624
  }
1570
- if (!fileBuffer) {
1571
- try {
1572
- fileBuffer = await this.readStaticBinaryFile(filePath);
1573
- } catch (err) {
1574
- console.error("Val: unexpected error while reading image / file:", filePath, {
1575
- 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
+ }
1576
1638
  });
1577
1639
  }
1640
+ } else {
1641
+ console.error("Val: unexpected while getting public image / file (file reference did not start with /public)", fileRef);
1578
1642
  }
1579
1643
  if (!fileBuffer) {
1580
1644
  revalidatedValidationErrors[errorSourcePath].push({
@@ -1721,7 +1785,7 @@ class ValServer {
1721
1785
  const moduleId = moduleIdStr;
1722
1786
  const serializedModuleContent = await this.applyAllPatchesThenValidate(moduleId, filterPatchesByModuleIdRes.data.patches ||
1723
1787
  // TODO: refine to ModuleId and PatchId when parsing
1724
- patchIdsByModuleId, patchesById, fileUpdates, true, cookies, requestHeaders);
1788
+ patchIdsByModuleId, patchesById, fileUpdates, cookies, requestHeaders, true, true, true, true);
1725
1789
  if (serializedModuleContent.errors) {
1726
1790
  validationErrorsByModuleId[moduleId] = serializedModuleContent;
1727
1791
  }
@@ -2225,8 +2289,8 @@ class LocalValServer extends ValServer {
2225
2289
  // No RemoteFS so nothing to ensure
2226
2290
  return result.ok(undefined);
2227
2291
  }
2228
- getModule(moduleId) {
2229
- return this.options.service.get(moduleId);
2292
+ getModule(moduleId, options) {
2293
+ return this.options.service.get(moduleId, "", options);
2230
2294
  }
2231
2295
  async execCommit(patches) {
2232
2296
  for (const [patchId, moduleId, patch] of patches) {
@@ -2514,11 +2578,11 @@ class ProxyValServer extends ValServer {
2514
2578
 
2515
2579
  /** Remote FS dependent methods: */
2516
2580
 
2517
- async getModule(moduleId) {
2581
+ async getModule(moduleId, options) {
2518
2582
  if (!this.lazyService) {
2519
2583
  this.lazyService = await createService(this.cwd, this.apiOptions, this.remoteFS);
2520
2584
  }
2521
- return this.lazyService.get(moduleId);
2585
+ return this.lazyService.get(moduleId, "", options);
2522
2586
  }
2523
2587
  execCommit(patches, cookies) {
2524
2588
  return withAuth(this.options.valSecret, cookies, "execCommit", async ({
@@ -3380,7 +3444,8 @@ function createValApiRouter(route, valServerPromise, convert) {
3380
3444
  return withTreePath(path, TREE_PATH_PREFIX)(async treePath => convert(await valServer.getTree(treePath, {
3381
3445
  patch: url.searchParams.get("patch") || undefined,
3382
3446
  schema: url.searchParams.get("schema") || undefined,
3383
- source: url.searchParams.get("source") || undefined
3447
+ source: url.searchParams.get("source") || undefined,
3448
+ validate: url.searchParams.get("validate") || undefined
3384
3449
  }, getCookies(req, [VAL_SESSION_COOKIE]), requestHeaders)));
3385
3450
  } else if (method === "GET" && path.startsWith(PATCHES_PATH_PREFIX)) {
3386
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.17",
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.17",
28
- "@valbuild/shared": "~0.60.17",
29
- "@valbuild/ui": "~0.60.17",
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",