diff --git a/docs/build/miscellaneous.mdx b/docs/build/miscellaneous.mdx
index b5a8f77..f68c0c8 100644
--- a/docs/build/miscellaneous.mdx
+++ b/docs/build/miscellaneous.mdx
@@ -1,6 +1,54 @@
# Miscellaneous
+## Ajv JSON Schema Validator
+[Ajv](https://ajv.js.org/), one of the best JSON schema validators, is
+extremely useful in the development of backend APIs for data validation. But Ajv
+is not supported by many edge environments, including Vercel, because of it's
+usage of `eval`. However, the SherpaJS compiler will pre-compile standalone Ajv
+schema, so schema validation can be done on the edge.
+
+
+
+### Example Static Schema
+Start by creating a `schema.json` file containing the desired schema.
+```json title="src/foo.schema.json"
+{
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "number"
+ }
+ },
+ "required": ["foo"],
+ "additionalProperties": false
+}
+```
+
+Then import your schema (in any script), and write the schema in the `AJV`
+function, provided by Sherpa Core.
+```typescript title="routes/index.ts"
+import { Request, Response, AJV } from "sherpa-core";
+import schemaFoo from "../../src/foo.schema.json";
+const validatorFoo = AJV(schemaFoo);
+
+export function POST(request:Request) {
+ try {
+ if (!validatorFoo(request.body)) {
+ return Response.text(JSON.stringify(validatorFoo.errors));
+ }
+ return Response.text("OK");
+ } catch (e) {
+ console.log(e);
+ return Response.text(e.toString());
+ }
+}
+```
+
+
+
+
+
## Environment Variables
Environment variables are a key part of configuring your SherpaJS application.
They allow you to set various configuration options and secrets without
diff --git a/package-lock.json b/package-lock.json
index aad2e4e..294af46 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,8 @@
"version": "0.0.0",
"license": "ISC",
"dependencies": {
+ "@offen/esbuild-plugin-jsonschema": "^1.1.0",
+ "ajv": "^8.13.0",
"checksum": "^1.0.0",
"chokidar": "^3.6.0",
"colorette": "^2.0.20",
@@ -37,6 +39,22 @@
"node": ">=20"
}
},
+ "../esbuild-plugin-jsonschema": {
+ "name": "@offen/esbuild-plugin-jsonschema",
+ "version": "1.1.0",
+ "extraneous": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^8.0.5",
+ "ajv-formats": "^2.0.2"
+ },
+ "devDependencies": {
+ "@studio/changes": "^2.0.1",
+ "esbuild": "^0.12.5",
+ "standard": "^16.0.3",
+ "tape": "^5.1.1"
+ }
+ },
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
@@ -1604,6 +1622,15 @@
"node": ">= 8"
}
},
+ "node_modules/@offen/esbuild-plugin-jsonschema": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@offen/esbuild-plugin-jsonschema/-/esbuild-plugin-jsonschema-1.1.0.tgz",
+ "integrity": "sha512-C7uv58cVINPCcbegGJxVVPRTxt+Cg5OUZxw4tEjZFIXKqWGi1oRfesKn2/TCSnr/LI/6RLhskLutAMtETr/aaw==",
+ "dependencies": {
+ "ajv": "^8.0.5",
+ "ajv-formats": "^2.0.2"
+ }
+ },
"node_modules/@sinclair/typebox": {
"version": "0.27.8",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz",
@@ -2060,6 +2087,37 @@
"node": ">=0.4.0"
}
},
+ "node_modules/ajv": {
+ "version": "8.13.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz",
+ "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.3",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.4.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ajv-formats": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz",
+ "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==",
+ "dependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependencies": {
+ "ajv": "^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ajv": {
+ "optional": true
+ }
+ }
+ },
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
@@ -3040,8 +3098,7 @@
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
- "dev": true
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-glob": {
"version": "3.3.2",
@@ -4184,6 +4241,11 @@
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
"dev": true
},
+ "node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug=="
+ },
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
@@ -4729,7 +4791,6 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -4796,6 +4857,14 @@
"node": ">=0.10.0"
}
},
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -5340,7 +5409,6 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
- "dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
diff --git a/package.json b/package.json
index 9b54929..aea2b1c 100644
--- a/package.json
+++ b/package.json
@@ -46,6 +46,8 @@
},
"license": "ISC",
"dependencies": {
+ "@offen/esbuild-plugin-jsonschema": "^1.1.0",
+ "ajv": "^8.13.0",
"checksum": "^1.0.0",
"chokidar": "^3.6.0",
"colorette": "^2.0.20",
diff --git a/src/compiler/utilities/tooling/index.ts b/src/compiler/utilities/tooling/index.ts
index da89525..044bbde 100644
--- a/src/compiler/utilities/tooling/index.ts
+++ b/src/compiler/utilities/tooling/index.ts
@@ -20,6 +20,7 @@ import { typeValidation } from "./type-validation/index.js";
import { getEnvironmentVariables } from "./dot-env/index.js";
import { ExportLoaderModule, getExportedLoader } from "./exported-loader/index.js";
import { ExportedVariable, getExportedVariables } from "./exported-variables/index.js";
+import jsonschemaPlugin from "@offen/esbuild-plugin-jsonschema";
export type { ExportLoaderModule, ExportedVariable };
@@ -65,6 +66,7 @@ export class Tooling {
...DEFAULT_ESBUILD_TARGET,
format: "cjs",
entryPoints: [filepath],
+ plugins: [jsonschemaPlugin()],
write: false
});
@@ -80,6 +82,7 @@ export class Tooling {
...DEFAULT_ESBUILD_TARGET,
...props.options?.developer?.bundler?.esbuild,
...props.esbuild,
+ plugins: [jsonschemaPlugin()],
stdin: {
contents: props.buffer,
resolveDir: props.resolve ? path.resolve(props.resolve) : undefined,
diff --git a/src/native/ajv/index.ts b/src/native/ajv/index.ts
new file mode 100644
index 0000000..957770d
--- /dev/null
+++ b/src/native/ajv/index.ts
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 Sellers Industries, Inc.
+ * distributed under the MIT License
+ *
+ * author: Evan Sellers
+ * date: Wed May 22 2024
+ * file: index.ts
+ * project: SherpaJS - Module Microservice Platform
+ * purpose: AJV Wrapper
+ *
+ */
+
+
+import { ValidateFunction, Schema, JSONSchemaType } from "ajv";
+
+
+export function AJV(schema:Schema|JSONSchemaType):ValidateFunction {
+ return schema as ValidateFunction;
+}
+
+
+// The grace of the Lord Jesus Christ be with your spirit. Amen.
+// - Philippians 4:23
diff --git a/src/native/index.ts b/src/native/index.ts
index 5dec1c2..b3b60d3 100644
--- a/src/native/index.ts
+++ b/src/native/index.ts
@@ -11,6 +11,7 @@
*/
+import { AJV } from "./ajv/index.js";
import { Parameters } from "./parameters/index.js";
import { Headers } from "./headers/index.js";
import { Body, BodyType } from "./model.js";
@@ -20,6 +21,7 @@ import { CreateModuleInterface, Method, ModuleInterface } from "../compiler/mode
export {
+ AJV,
Headers,
Parameters,
Method,
diff --git a/tests/endpoints/server/routes/ajv/index.ts b/tests/endpoints/server/routes/ajv/index.ts
new file mode 100644
index 0000000..d0448ad
--- /dev/null
+++ b/tests/endpoints/server/routes/ajv/index.ts
@@ -0,0 +1,17 @@
+import { Request, Response, AJV } from "../../../../../index.js";
+import schemaFoo from "../../src/foo.schema.json";
+const validatorFoo = AJV(schemaFoo);
+
+
+export function POST(request:Request) {
+ try {
+ if (!validatorFoo(request.body)) {
+ return Response.text(JSON.stringify(validatorFoo.errors));
+ }
+ return Response.text("OK");
+ } catch (e) {
+ console.log(e);
+ return Response.text(e.toString());
+ }
+}
+
diff --git a/tests/endpoints/server/src/foo.schema.json b/tests/endpoints/server/src/foo.schema.json
new file mode 100644
index 0000000..6aa7683
--- /dev/null
+++ b/tests/endpoints/server/src/foo.schema.json
@@ -0,0 +1,10 @@
+{
+ "type": "object",
+ "properties": {
+ "foo": {
+ "type": "number"
+ }
+ },
+ "required": ["foo"],
+ "additionalProperties": false
+}
\ No newline at end of file