diff --git a/src/bundle.ts b/src/bundle.ts index 7d59698..1a3e1df 100644 --- a/src/bundle.ts +++ b/src/bundle.ts @@ -20,7 +20,11 @@ export async function publishProject( sandboxFactory = getDefaultSandbox, ): Promise { projectPath = await Deno.realPath(projectPath); - const projectJson = await getProjectJson(projectPath, sandboxFactory); + const projectJson = await getProjectJson( + projectPath, + "local", + sandboxFactory, + ); let code = await generateBundle(projectPath); const vectorDbPath = projectJson.vectorStorage?.path; if (vectorDbPath) { diff --git a/src/project/project.ts b/src/project/project.ts index 6ab37fd..99672f1 100644 --- a/src/project/project.ts +++ b/src/project/project.ts @@ -32,6 +32,11 @@ export const VectorConfig = Type.Object({ }); export const Project = Type.Object({ + // Note: If a new spec version is introduced Type.TemplateLiteral could be used here + specVersion: Type.Literal( + "0.0.1", + { description: "The specification version of the project structure." }, + ), model: Type.String({ description: "The llm model to use", }), @@ -71,8 +76,9 @@ export type IProjectEntrypoint = Static< IProjectEntry >; -export function validateProject(project: unknown): void { - return Value.Assert(Project, project); +export function validateProject(project: unknown): project is IProject { + Value.Assert(Project, project); + return true; } function validateProjectEntry(entry: unknown): entry is IProjectEntry { @@ -109,10 +115,9 @@ export async function getProjectFromEntrypoint( // Check that the constructed project is valid const project = await entrypoint.projectFactory(config); - validateProject(project); - - return project; - } else { - throw new Error("Unable to validate project"); + if (validateProject(project)) { + return project; + } } + throw new Error("Unable to validate project"); } diff --git a/src/sandbox/webWorker/webWorker.ts b/src/sandbox/webWorker/webWorker.ts index 426a070..88f544d 100644 --- a/src/sandbox/webWorker/webWorker.ts +++ b/src/sandbox/webWorker/webWorker.ts @@ -19,6 +19,7 @@ import { type IProjectEntrypoint, } from "../../project/project.ts"; import type { IContext } from "../../context/context.ts"; +import { PrettyTypeboxError } from "../../util.ts"; const conn = rpc.createMessageConnection( new BrowserMessageReader(self), @@ -52,9 +53,16 @@ conn.onRequest(Init, async (config) => { throw new Error("Please call `load` first"); } - project ??= await getProjectFromEntrypoint(entrypoint, config); + try { + project ??= await getProjectFromEntrypoint(entrypoint, config); - return toJsonProject(); + return toJsonProject(); + } catch (e: unknown) { + if (e instanceof Error) { + throw PrettyTypeboxError(e, "Project validation failed"); + } + throw e; + } }); conn.onRequest(GetConfig, () => { diff --git a/src/util.ts b/src/util.ts index cfffe64..befcad7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,5 +1,5 @@ import type { Static, TSchema } from "@sinclair/typebox"; -import { Value } from "@sinclair/typebox/value"; +import { AssertError, Value } from "@sinclair/typebox/value"; import ora, { type Ora } from "ora"; import { brightBlue } from "@std/fmt/colors"; @@ -35,3 +35,22 @@ export function getPrompt(): string | null { // Possible sources where projects can be loaded from export type ProjectSource = "local" | "ipfs"; + +export function PrettyTypeboxError( + error: Error, + prefix = "Type Assertion Failed", +): Error { + if ( + error instanceof AssertError || error.constructor.name === "AssertError" + ) { + const errs = [...(error as AssertError).Errors()]; + + let msg = `${prefix}:\n`; + for (const e of errs) { + msg += `\t${e.path}: ${e.message}\n`; + } + return new Error(msg, { cause: error }); + } + + return error; +} diff --git a/subquery-delegator/index.ts b/subquery-delegator/index.ts index 5458108..63633c7 100644 --- a/subquery-delegator/index.ts +++ b/subquery-delegator/index.ts @@ -56,6 +56,7 @@ export const entrypoint: IProjectEntrypoint = { return { tools, + specVersion: "0.0.1", model: "llama3.1", vectorStorage: { type: "lancedb",