@valbuild/server 0.26.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/package.json +7 -4
  2. package/.babelrc.json +0 -5
  3. package/CHANGELOG.md +0 -0
  4. package/jest.config.js +0 -4
  5. package/src/LocalValServer.ts +0 -167
  6. package/src/ProxyValServer.ts +0 -542
  7. package/src/SerializedModuleContent.ts +0 -36
  8. package/src/Service.ts +0 -126
  9. package/src/ValFS.ts +0 -22
  10. package/src/ValFSHost.ts +0 -66
  11. package/src/ValModuleLoader.test.ts +0 -75
  12. package/src/ValModuleLoader.ts +0 -158
  13. package/src/ValQuickJSRuntime.ts +0 -85
  14. package/src/ValServer.ts +0 -24
  15. package/src/ValSourceFileHandler.ts +0 -57
  16. package/src/createFixPatch.ts +0 -170
  17. package/src/createRequestHandler.ts +0 -27
  18. package/src/expressHelpers.ts +0 -5
  19. package/src/getCompilerOptions.ts +0 -50
  20. package/src/hosting.ts +0 -290
  21. package/src/index.ts +0 -16
  22. package/src/jwt.ts +0 -93
  23. package/src/patch/ts/ops.test.ts +0 -937
  24. package/src/patch/ts/ops.ts +0 -897
  25. package/src/patch/ts/syntax.ts +0 -371
  26. package/src/patch/ts/valModule.test.ts +0 -26
  27. package/src/patch/ts/valModule.ts +0 -110
  28. package/src/patch/validation.ts +0 -81
  29. package/src/patchValFile.ts +0 -110
  30. package/src/readValFile.test.ts +0 -49
  31. package/src/readValFile.ts +0 -96
  32. package/test/example-projects/basic-next-javascript/jsconfig.json +0 -8
  33. package/test/example-projects/basic-next-javascript/package.json +0 -23
  34. package/test/example-projects/basic-next-javascript/pages/blogs.val.js +0 -20
  35. package/test/example-projects/basic-next-javascript/val.config.js +0 -4
  36. package/test/example-projects/basic-next-src-typescript/package.json +0 -23
  37. package/test/example-projects/basic-next-src-typescript/src/pages/blogs.val.ts +0 -20
  38. package/test/example-projects/basic-next-src-typescript/src/val.config.ts +0 -5
  39. package/test/example-projects/basic-next-src-typescript/tsconfig.json +0 -24
  40. package/test/example-projects/basic-next-typescript/package.json +0 -23
  41. package/test/example-projects/basic-next-typescript/pages/blogs.val.ts +0 -20
  42. package/test/example-projects/basic-next-typescript/tsconfig.json +0 -25
  43. package/test/example-projects/basic-next-typescript/val.config.ts +0 -5
  44. package/test/example-projects/typescript-description-files/README.md +0 -2
  45. package/test/example-projects/typescript-description-files/jsconfig.json +0 -8
  46. package/test/example-projects/typescript-description-files/package.json +0 -23
  47. package/test/example-projects/typescript-description-files/pages/blogs.val.d.ts +0 -7
  48. package/test/example-projects/typescript-description-files/pages/blogs.val.js +0 -19
  49. package/test/example-projects/typescript-description-files/val.config.d.ts +0 -3
  50. package/test/example-projects/typescript-description-files/val.config.js +0 -5
  51. package/tsconfig.json +0 -12
@@ -1,542 +0,0 @@
1
- import express from "express";
2
- import crypto from "crypto";
3
- import { decodeJwt, encodeJwt, getExpire } from "./jwt";
4
- import { PatchJSON } from "./patch/validation";
5
- import { ValServer } from "./ValServer";
6
- import { z } from "zod";
7
- import { Internal } from "@valbuild/core";
8
- import { Readable } from "stream";
9
-
10
- const VAL_SESSION_COOKIE = Internal.VAL_SESSION_COOKIE;
11
- const VAL_STATE_COOKIE = Internal.VAL_STATE_COOKIE;
12
- const VAL_ENABLED_COOKIE = Internal.VAL_ENABLE_COOKIE_NAME;
13
-
14
- export type ProxyValServerOptions = {
15
- apiKey: string;
16
- route: string;
17
- valSecret: string;
18
- valBuildUrl: string;
19
- valContentUrl: string;
20
- gitCommit: string;
21
- gitBranch: string;
22
- valName: string;
23
- valEnableRedirectUrl?: string;
24
- valDisableRedirectUrl?: string;
25
- };
26
-
27
- class BrowserReadableStreamWrapper extends Readable {
28
- private reader: ReadableStreamDefaultReader<Uint8Array>;
29
-
30
- constructor(readableStream: ReadableStream<Uint8Array>) {
31
- super();
32
- this.reader = readableStream.getReader();
33
- }
34
-
35
- _read() {
36
- this.reader
37
- .read()
38
- .then(({ done, value }) => {
39
- if (done) {
40
- this.push(null); // No more data to read
41
- } else {
42
- this.push(Buffer.from(value));
43
- }
44
- })
45
- .catch((error) => {
46
- this.emit("error", error);
47
- });
48
- }
49
- }
50
-
51
- export class ProxyValServer implements ValServer {
52
- constructor(readonly options: ProxyValServerOptions) {}
53
-
54
- async getFiles(req: express.Request, res: express.Response): Promise<void> {
55
- return this.withAuth(req, res, async (data) => {
56
- const url = new URL(
57
- `/v1/files/${this.options.valName}/${req.params["0"]}`,
58
- this.options.valContentUrl
59
- );
60
- if (typeof req.query.sha256 === "string") {
61
- url.searchParams.append("sha256", req.query.sha256 as string);
62
- } else {
63
- console.warn("Missing sha256 query param");
64
- }
65
- const fetchRes = await fetch(url, {
66
- headers: this.getAuthHeaders(data.token),
67
- });
68
- const contentType = fetchRes.headers.get("content-type");
69
- if (contentType !== null) {
70
- res.setHeader("Content-Type", contentType);
71
- }
72
- const contentLength = fetchRes.headers.get("content-length");
73
- if (contentLength !== null) {
74
- res.setHeader("Content-Length", contentLength);
75
- }
76
- if (fetchRes.ok) {
77
- if (fetchRes.body) {
78
- new BrowserReadableStreamWrapper(fetchRes.body).pipe(res);
79
- } else {
80
- console.warn("No body in response");
81
- res.sendStatus(500);
82
- }
83
- } else {
84
- res.sendStatus(fetchRes.status);
85
- }
86
- });
87
- }
88
-
89
- async authorize(req: express.Request, res: express.Response): Promise<void> {
90
- const { redirect_to } = req.query;
91
- if (typeof redirect_to !== "string") {
92
- res.redirect(
93
- this.getAppErrorUrl("Login failed: missing redirect_to param")
94
- );
95
- return;
96
- }
97
- const token = crypto.randomUUID();
98
- const redirectUrl = new URL(redirect_to);
99
- const appAuthorizeUrl = this.getAuthorizeUrl(
100
- `${redirectUrl.origin}/${this.options.route}`,
101
- token
102
- );
103
- res
104
- .cookie(VAL_STATE_COOKIE, createStateCookie({ redirect_to, token }), {
105
- httpOnly: true,
106
- sameSite: "lax",
107
- expires: new Date(Date.now() + 1000 * 60 * 60), // 1 hour
108
- })
109
- .redirect(appAuthorizeUrl);
110
- }
111
-
112
- async enable(req: express.Request, res: express.Response): Promise<void> {
113
- return enable(req, res, this.options.valEnableRedirectUrl);
114
- }
115
- async disable(req: express.Request, res: express.Response): Promise<void> {
116
- return disable(req, res, this.options.valEnableRedirectUrl);
117
- }
118
-
119
- async callback(req: express.Request, res: express.Response): Promise<void> {
120
- const { success: callbackReqSuccess, error: callbackReqError } =
121
- verifyCallbackReq(req.cookies[VAL_STATE_COOKIE], req.query);
122
- res.clearCookie(VAL_STATE_COOKIE); // we don't need this anymore
123
-
124
- if (callbackReqError !== null) {
125
- res.redirect(
126
- this.getAppErrorUrl(
127
- `Authorization callback failed. Details: ${callbackReqError}`
128
- )
129
- );
130
- return;
131
- }
132
-
133
- const data = await this.consumeCode(callbackReqSuccess.code);
134
- if (data === null) {
135
- res.redirect(this.getAppErrorUrl("Failed to exchange code for user"));
136
- return;
137
- }
138
- const exp = getExpire();
139
- const cookie = encodeJwt(
140
- {
141
- ...data,
142
- exp, // this is the client side exp
143
- },
144
- this.options.valSecret
145
- );
146
-
147
- res
148
- .cookie(VAL_SESSION_COOKIE, cookie, {
149
- httpOnly: true,
150
- sameSite: "strict",
151
- secure: true,
152
- expires: new Date(exp * 1000), // NOTE: this is not used for authorization, only for authentication
153
- })
154
- .redirect(callbackReqSuccess.redirect_uri || "/");
155
- }
156
-
157
- async logout(_req: express.Request, res: express.Response): Promise<void> {
158
- res
159
- .clearCookie(VAL_SESSION_COOKIE)
160
- .clearCookie(VAL_STATE_COOKIE)
161
- .sendStatus(200);
162
- }
163
-
164
- async withAuth<T>(
165
- req: express.Request,
166
- res: express.Response,
167
- handler: (data: IntegratedServerJwtPayload) => Promise<T>
168
- ): Promise<T | undefined> {
169
- const cookie = req.cookies[VAL_SESSION_COOKIE];
170
- if (typeof cookie === "string") {
171
- const verification = IntegratedServerJwtPayload.safeParse(
172
- decodeJwt(cookie, this.options.valSecret)
173
- );
174
- if (!verification.success) {
175
- res.sendStatus(401);
176
- return;
177
- }
178
- return handler(verification.data);
179
- } else {
180
- res.sendStatus(401);
181
- }
182
- }
183
-
184
- async session(req: express.Request, res: express.Response): Promise<void> {
185
- return this.withAuth(req, res, async (data) => {
186
- const url = new URL(
187
- `/api/val/${this.options.valName}/auth/session`,
188
- this.options.valBuildUrl
189
- );
190
- const fetchRes = await fetch(url, {
191
- headers: this.getAuthHeaders(data.token, "application/json"),
192
- });
193
- if (fetchRes.ok) {
194
- res
195
- .status(fetchRes.status)
196
- .json({ mode: "proxy", ...(await fetchRes.json()) });
197
- } else {
198
- res.sendStatus(fetchRes.status);
199
- }
200
- });
201
- }
202
-
203
- async getTree(req: express.Request, res: express.Response): Promise<void> {
204
- return this.withAuth(req, res, async (data) => {
205
- const { patch, schema, source } = req.query;
206
- const commit = this.options.gitCommit;
207
- if (!commit) {
208
- res.status(401).json({
209
- error:
210
- "Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT.",
211
- });
212
- return;
213
- }
214
- const params = new URLSearchParams({
215
- patch: (patch === "true").toString(),
216
- schema: (schema === "true").toString(),
217
- source: (source === "true").toString(),
218
- commit,
219
- });
220
- const url = new URL(
221
- `/v1/tree/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
222
- this.options.valContentUrl
223
- );
224
- const json = await fetch(url, {
225
- headers: this.getAuthHeaders(data.token, "application/json"),
226
- }).then((res) => res.json());
227
- res.send(json);
228
- });
229
- }
230
-
231
- async postPatches(
232
- req: express.Request<{ 0: string }>,
233
- res: express.Response
234
- ): Promise<void> {
235
- const commit = this.options.gitCommit;
236
- if (!commit) {
237
- res.status(401).json({
238
- error:
239
- "Could not detect the git commit. Check if env is missing VAL_GIT_COMMIT.",
240
- });
241
- return;
242
- }
243
- const params = new URLSearchParams({
244
- commit,
245
- });
246
- this.withAuth(req, res, async ({ token }) => {
247
- // First validate that the body has the right structure
248
- const patchJSON = z.record(PatchJSON).safeParse(req.body);
249
- if (!patchJSON.success) {
250
- res.status(401).json(patchJSON.error.issues);
251
- return;
252
- }
253
- // Then parse/validate
254
- // TODO:
255
- const patch = patchJSON.data;
256
- // const patch = parsePatch(patchJSON.data);
257
- // if (result.isErr(patch)) {
258
- // res.status(401).json(patch.error);
259
- // return;
260
- // }
261
- const url = new URL(
262
- `/v1/patches/${this.options.valName}/heads/${this.options.gitBranch}/${req.params["0"]}/?${params}`,
263
- this.options.valContentUrl
264
- );
265
- // Proxy patch to val.build
266
- const fetchRes = await fetch(url, {
267
- method: "POST",
268
- headers: this.getAuthHeaders(token, "application/json"),
269
- body: JSON.stringify(patch),
270
- });
271
- if (fetchRes.ok) {
272
- res.status(fetchRes.status).json(await fetchRes.json());
273
- } else {
274
- res.sendStatus(fetchRes.status);
275
- }
276
- }).catch((e) => {
277
- res.status(500).send({ error: { message: e?.message, status: 500 } });
278
- });
279
- }
280
-
281
- async commit(req: express.Request, res: express.Response): Promise<void> {
282
- this.withAuth(req, res, async ({ token }) => {
283
- const url = new URL(
284
- `/api/val/commit/${encodeURIComponent(this.options.gitBranch)}`,
285
- this.options.valBuildUrl
286
- );
287
- const fetchRes = await fetch(url, {
288
- method: "POST",
289
- headers: this.getAuthHeaders(token),
290
- });
291
- if (fetchRes.ok) {
292
- res.status(fetchRes.status).json(await fetchRes.json());
293
- } else {
294
- res.sendStatus(fetchRes.status);
295
- }
296
- });
297
- }
298
-
299
- private getAuthHeaders(
300
- token: string,
301
- type?: "application/json" | "application/json-patch+json"
302
- ):
303
- | { Authorization: string }
304
- | { "Content-Type": string; Authorization: string } {
305
- if (!type) {
306
- return {
307
- Authorization: `Bearer ${token}`,
308
- };
309
- }
310
- return {
311
- "Content-Type": type,
312
- Authorization: `Bearer ${token}`,
313
- };
314
- }
315
-
316
- private async consumeCode(code: string): Promise<{
317
- sub: string;
318
- exp: number;
319
- org: string;
320
- project: string;
321
- token: string;
322
- } | null> {
323
- const url = new URL(
324
- `/api/val/${this.options.valName}/auth/token`,
325
- this.options.valBuildUrl
326
- );
327
- url.searchParams.set("code", encodeURIComponent(code));
328
- return fetch(url, {
329
- method: "POST",
330
- headers: this.getAuthHeaders(this.options.apiKey, "application/json"), // NOTE: we use apiKey as auth on this endpoint (we do not have a token yet)
331
- })
332
- .then(async (res) => {
333
- if (res.status === 200) {
334
- const token = await res.text();
335
- const verification = ValAppJwtPayload.safeParse(decodeJwt(token));
336
- if (!verification.success) {
337
- return null;
338
- }
339
- return {
340
- ...verification.data,
341
- token,
342
- };
343
- } else {
344
- console.debug("Failed to get data from code: ", res.status);
345
- return null;
346
- }
347
- })
348
- .catch((err) => {
349
- console.debug("Failed to get user from code: ", err);
350
- return null;
351
- });
352
- }
353
-
354
- private getAuthorizeUrl(publicValApiRoute: string, token: string): string {
355
- const url = new URL(
356
- `/auth/${this.options.valName}/authorize`,
357
- this.options.valBuildUrl
358
- );
359
- url.searchParams.set(
360
- "redirect_uri",
361
- encodeURIComponent(`${publicValApiRoute}/callback`)
362
- );
363
- url.searchParams.set("state", token);
364
- return url.toString();
365
- }
366
-
367
- private getAppErrorUrl(error: string): string {
368
- const url = new URL("/authorize", this.options.valBuildUrl);
369
- url.searchParams.set("error", encodeURIComponent(error));
370
- return url.toString();
371
- }
372
- }
373
-
374
- function verifyCallbackReq(
375
- stateCookie: string,
376
- queryParams: Record<string, unknown>
377
- ):
378
- | {
379
- success: { code: string; redirect_uri?: string };
380
- error: null;
381
- }
382
- | { success: false; error: string } {
383
- if (typeof stateCookie !== "string") {
384
- return { success: false, error: "No state cookie" };
385
- }
386
-
387
- const { code, state: tokenFromQuery } = queryParams;
388
-
389
- if (typeof code !== "string") {
390
- return { success: false, error: "No code query param" };
391
- }
392
- if (typeof tokenFromQuery !== "string") {
393
- return { success: false, error: "No state query param" };
394
- }
395
-
396
- const { success: cookieStateSuccess, error: cookieStateError } =
397
- getStateFromCookie(stateCookie);
398
-
399
- if (cookieStateError !== null) {
400
- return { success: false, error: cookieStateError };
401
- }
402
-
403
- if (cookieStateSuccess.token !== tokenFromQuery) {
404
- return { success: false, error: "Invalid state token" };
405
- }
406
-
407
- return {
408
- success: { code, redirect_uri: cookieStateSuccess.redirect_to },
409
- error: null,
410
- };
411
- }
412
-
413
- type StateCookie = {
414
- redirect_to: string;
415
- token: string;
416
- };
417
-
418
- function getStateFromCookie(stateCookie: string):
419
- | {
420
- success: StateCookie;
421
- error: null;
422
- }
423
- | { success: false; error: string } {
424
- try {
425
- const decoded = Buffer.from(stateCookie, "base64").toString("utf8");
426
- const parsed = JSON.parse(decoded) as unknown;
427
-
428
- if (!parsed) {
429
- return {
430
- success: false,
431
- error: "Invalid state cookie: could not parse",
432
- };
433
- }
434
- if (typeof parsed !== "object") {
435
- return {
436
- success: false,
437
- error: "Invalid state cookie: parsed object is not an object",
438
- };
439
- }
440
- if ("token" in parsed && "redirect_to" in parsed) {
441
- const { token, redirect_to } = parsed;
442
- if (typeof token !== "string") {
443
- return {
444
- success: false,
445
- error: "Invalid state cookie: no token in parsed object",
446
- };
447
- }
448
- if (typeof redirect_to !== "string") {
449
- return {
450
- success: false,
451
- error: "Invalid state cookie: no redirect_to in parsed object",
452
- };
453
- }
454
- return {
455
- success: {
456
- token,
457
- redirect_to,
458
- },
459
- error: null,
460
- };
461
- } else {
462
- return {
463
- success: false,
464
- error: "Invalid state cookie: no token or redirect_to in parsed object",
465
- };
466
- }
467
- } catch (err) {
468
- return {
469
- success: false,
470
- error: "Invalid state cookie: could not parse",
471
- };
472
- }
473
- }
474
-
475
- export async function enable(
476
- req: express.Request,
477
- res: express.Response,
478
- redirectUrl?: string
479
- ): Promise<void> {
480
- const { redirect_to } = req.query;
481
- if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
482
- let redirectUrlToUse = redirect_to || "/";
483
- if (redirectUrl) {
484
- redirectUrlToUse =
485
- redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
486
- }
487
- res
488
- .cookie(VAL_ENABLED_COOKIE, "true", {
489
- httpOnly: false,
490
- sameSite: "lax",
491
- })
492
- .redirect(redirectUrlToUse);
493
- } else {
494
- res.sendStatus(400);
495
- }
496
- }
497
-
498
- export async function disable(
499
- req: express.Request,
500
- res: express.Response,
501
- redirectUrl?: string
502
- ): Promise<void> {
503
- const { redirect_to } = req.query;
504
- if (typeof redirect_to === "string" || typeof redirect_to === "undefined") {
505
- let redirectUrlToUse = redirect_to || "/";
506
- if (redirectUrl) {
507
- redirectUrlToUse =
508
- redirectUrl + "?redirect_to=" + encodeURIComponent(redirectUrlToUse);
509
- }
510
- res
511
- .cookie(VAL_ENABLED_COOKIE, "false", {
512
- httpOnly: false,
513
- sameSite: "lax",
514
- })
515
- .redirect(redirectUrlToUse);
516
- } else {
517
- res.sendStatus(400);
518
- }
519
- }
520
-
521
- function createStateCookie(state: StateCookie): string {
522
- return Buffer.from(JSON.stringify(state), "utf8").toString("base64");
523
- }
524
-
525
- const ValAppJwtPayload = z.object({
526
- sub: z.string(),
527
- exp: z.number(),
528
- project: z.string(),
529
- org: z.string(),
530
- });
531
- type ValAppJwtPayload = z.infer<typeof ValAppJwtPayload>;
532
-
533
- const IntegratedServerJwtPayload = z.object({
534
- sub: z.string(),
535
- exp: z.number(),
536
- token: z.string(),
537
- org: z.string(),
538
- project: z.string(),
539
- });
540
- export type IntegratedServerJwtPayload = z.infer<
541
- typeof IntegratedServerJwtPayload
542
- >;
@@ -1,36 +0,0 @@
1
- import {
2
- type Source,
3
- type SerializedSchema,
4
- ValidationErrors,
5
- } from "@valbuild/core";
6
- import { ModuleId, type SourcePath } from "@valbuild/core/src/val";
7
-
8
- export const FATAL_ERROR_TYPES = [
9
- "no-schema",
10
- "no-source",
11
- "invalid-id",
12
- "no-module",
13
- ] as const;
14
- export type FatalErrorType = (typeof FATAL_ERROR_TYPES)[number];
15
-
16
- export type SerializedModuleContent =
17
- | {
18
- source: Source;
19
- schema: SerializedSchema;
20
- path: SourcePath;
21
- errors: false;
22
- }
23
- | {
24
- source?: Source;
25
- schema?: SerializedSchema;
26
- path: SourcePath;
27
- errors: {
28
- invalidModuleId?: ModuleId;
29
- validation?: ValidationErrors;
30
- fatal?: {
31
- message: string;
32
- stack?: string;
33
- type?: FatalErrorType;
34
- }[];
35
- };
36
- };
package/src/Service.ts DELETED
@@ -1,126 +0,0 @@
1
- import { newQuickJSWASMModule, QuickJSRuntime } from "quickjs-emscripten";
2
- import { patchValFile } from "./patchValFile";
3
- import { readValFile } from "./readValFile";
4
- import { Patch } from "@valbuild/core/patch";
5
- import { ValModuleLoader } from "./ValModuleLoader";
6
- import { newValQuickJSRuntime } from "./ValQuickJSRuntime";
7
- import { ValSourceFileHandler } from "./ValSourceFileHandler";
8
- import ts from "typescript";
9
- import { getCompilerOptions } from "./getCompilerOptions";
10
- import { IValFSHost } from "./ValFSHost";
11
- import fs from "fs";
12
- import { SerializedModuleContent } from "./SerializedModuleContent";
13
- import {
14
- ModuleId,
15
- ModulePath,
16
- Internal,
17
- SourcePath,
18
- Schema,
19
- } from "@valbuild/core";
20
-
21
- export type ServiceOptions = {
22
- /**
23
- * Relative path to the val.config.js file from the root directory.
24
- *
25
- * @example "./val.config"
26
- */
27
- valConfigPath: string;
28
- /**
29
- * Disable cache for transpilation
30
- *
31
- * @default false
32
- * */
33
- disableCache?: boolean;
34
- };
35
-
36
- export async function createService(
37
- projectRoot: string,
38
- opts: ServiceOptions,
39
- host: IValFSHost = {
40
- ...ts.sys,
41
- writeFile: fs.writeFileSync,
42
- },
43
- loader?: ValModuleLoader
44
- ): Promise<Service> {
45
- const compilerOptions = getCompilerOptions(projectRoot, host);
46
- const sourceFileHandler = new ValSourceFileHandler(
47
- projectRoot,
48
- compilerOptions,
49
- host
50
- );
51
- const module = await newQuickJSWASMModule();
52
- const runtime = await newValQuickJSRuntime(
53
- module,
54
- loader ||
55
- new ValModuleLoader(projectRoot, compilerOptions, sourceFileHandler, host)
56
- );
57
- return new Service(opts, sourceFileHandler, runtime);
58
- }
59
-
60
- export class Service {
61
- readonly valConfigPath: string;
62
-
63
- constructor(
64
- { valConfigPath }: ServiceOptions,
65
- private readonly sourceFileHandler: ValSourceFileHandler,
66
- private readonly runtime: QuickJSRuntime
67
- ) {
68
- this.valConfigPath = valConfigPath;
69
- }
70
-
71
- async get(
72
- moduleId: ModuleId,
73
- modulePath: ModulePath
74
- ): Promise<SerializedModuleContent> {
75
- const valModule = await readValFile(
76
- moduleId,
77
- this.valConfigPath,
78
- this.runtime
79
- );
80
-
81
- if (valModule.source && valModule.schema) {
82
- const resolved = Internal.resolvePath(
83
- modulePath,
84
- valModule.source,
85
- valModule.schema
86
- );
87
- const sourcePath = (
88
- resolved.path ? [moduleId, resolved.path].join(".") : moduleId
89
- ) as SourcePath;
90
- return {
91
- path: sourcePath,
92
- schema:
93
- resolved.schema instanceof Schema
94
- ? resolved.schema.serialize()
95
- : resolved.schema,
96
- source: resolved.source,
97
- errors:
98
- valModule.errors && valModule.errors.validation
99
- ? {
100
- validation: valModule.errors.validation || undefined,
101
- fatal: valModule.errors.fatal || undefined,
102
- }
103
- : false,
104
- };
105
- } else {
106
- return valModule;
107
- }
108
- }
109
-
110
- async patch(
111
- moduleId: string,
112
- patch: Patch
113
- ): Promise<SerializedModuleContent> {
114
- return patchValFile(
115
- moduleId,
116
- this.valConfigPath,
117
- patch,
118
- this.sourceFileHandler,
119
- this.runtime
120
- );
121
- }
122
-
123
- dispose() {
124
- this.runtime.dispose();
125
- }
126
- }