@stacksjs/ai 0.75.191 → 0.75.193
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.
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +1 -1
- package/dist/schema.js +1 -1
- package/package.json +5 -5
package/dist/mcp-server.d.ts
CHANGED
|
@@ -18,11 +18,14 @@ export declare interface MCPServerTool<Principal> {
|
|
|
18
18
|
description: string
|
|
19
19
|
inputSchema: JsonObject
|
|
20
20
|
outputSchema?: JsonObject
|
|
21
|
+
securitySchemes?: Array<{ type: 'oauth2', scopes: string[] } | { type: 'noauth' }>
|
|
22
|
+
_meta?: Record<string, unknown>
|
|
21
23
|
annotations?: { readOnlyHint?: boolean, destructiveHint?: boolean, idempotentHint?: boolean, openWorldHint?: boolean }
|
|
22
24
|
available?: (principal: Principal) => boolean
|
|
23
25
|
execute: (args: JsonObject, context: MCPServerContext<Principal>) => MaybePromise<MCPServerToolResult>
|
|
24
26
|
}
|
|
25
27
|
export declare interface MCPServerToolResult {
|
|
28
|
+
_meta?: Record<string, unknown>
|
|
26
29
|
content: Array<{ type: 'text', text: string }>
|
|
27
30
|
structuredContent?: JsonObject
|
|
28
31
|
isError?: boolean
|
|
@@ -52,6 +55,7 @@ export declare interface MCPServerOptions<Principal> {
|
|
|
52
55
|
authenticate: (request: Request) => MaybePromise<Principal | Response>
|
|
53
56
|
responseHeaders?: (principal: Principal) => HeadersInit
|
|
54
57
|
allowedOrigins?: readonly string[]
|
|
58
|
+
resourceMetadataUrl?: string
|
|
55
59
|
maxBodyBytes?: number
|
|
56
60
|
pageSize?: number
|
|
57
61
|
tools?: readonly MCPServerTool<Principal>[]
|
package/dist/mcp-server.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{readJsonBody,RequestBodyError}from"@stacksjs/router/body";import{validateSchema}from"./schema";export const MCP_PROTOCOL_VERSIONS=["2025-11-25","2025-06-18","2025-03-26"];class ProtocolError extends Error{code;constructor(code,message){super(message);this.code=code}}const invalid=(message)=>{throw new ProtocolError(-32602,message)};function record(value){return value!==null&&typeof value==="object"&&!Array.isArray(value)}function json(body,status=200,headers){const response=Response.json(body,{status,headers});response.headers.set("Cache-Control","no-store");response.headers.set("X-Content-Type-Options","nosniff");return response}function failure(id,code,message,status=200){return json({jsonrpc:"2.0",id,error:{code,message}},status)}function accepts(header,type){return(header??"").split(",").some((part)=>{const[media,...params]=part.trim().toLowerCase().split(";"),quality=params.find((param)=>param.trim().startsWith("q="))?.trim().slice(2);return media?.trim()===type&&(quality===void 0||Number(quality)>0&&Number(quality)<=1)})}function checkSchema(schema){const supported=new Set(["$schema","type","description","title","default","examples","enum","const","properties","required","additionalProperties","items","minimum","maximum","minLength","maxLength","minItems","maxItems","pattern"]);for(const key of Object.keys(schema))if(!supported.has(key))throw TypeError(`Unsupported MCP schema keyword: ${key}; use an execute validator for domain rules`);if(schema.additionalProperties!==void 0&&typeof schema.additionalProperties!=="boolean")throw TypeError("MCP additionalProperties must be a boolean");if(schema.pattern!==void 0)new RegExp(String(schema.pattern));if(record(schema.properties))for(const child of Object.values(schema.properties)){if(!record(child))throw TypeError("MCP property schemas must be objects");checkSchema(child)}if(schema.items!==void 0){if(!record(schema.items))throw TypeError("MCP items must be an object schema");checkSchema(schema.items)}}export function createMCPServer(options){const tools=[...options.tools??[]],resources=[...options.resources??[]],prompts=[...options.prompts??[]],pageSize=options.pageSize??50,maxBodyBytes=options.maxBodyBytes??1048576;if(!Number.isSafeInteger(pageSize)||pageSize<1||pageSize>1000)throw RangeError("MCP pageSize must be between 1 and 1000");if(!Number.isSafeInteger(maxBodyBytes)||maxBodyBytes<1)throw RangeError("MCP maxBodyBytes must be positive");for(const list of[tools,prompts]){const names=new Set;for(const item of list){if(!/^[\w.-]{1,128}$/.test(item.name)||names.has(item.name))throw TypeError(`Invalid or duplicate MCP name: ${item.name}`);names.add(item.name)}}for(const origin of options.allowedOrigins??[])if(origin==="null"||new URL(origin).origin!==origin||!/^https?:/.test(origin))throw TypeError("MCP allowedOrigins must contain exact HTTP(S) origins");if(new Set(resources.map((item)=>item.uri)).size!==resources.length)throw TypeError("Duplicate MCP resource URI");for(const tool of tools){if(tool.inputSchema.type!=="object")throw TypeError("MCP tool inputSchema must be an object schema");checkSchema(tool.inputSchema);if(tool.outputSchema){if(tool.outputSchema.type!=="object")throw TypeError("MCP tool outputSchema must be an object schema");checkSchema(tool.outputSchema)}}function page(items,params,kind){let offset=0;if(params.cursor!==void 0){if(typeof params.cursor!=="string")invalid("Invalid pagination cursor");const match=Buffer.from(params.cursor,"base64url").toString().match(new RegExp(`^${kind}:(\\d+)$`));if(!match)invalid("Invalid pagination cursor");offset=Number(match[1]);if(!Number.isSafeInteger(offset)||offset<1||offset>=items.length||offset%pageSize!==0)invalid("Invalid pagination cursor")}const next=offset+pageSize;return{[kind]:items.slice(offset,next),...next<items.length?{nextCursor:Buffer.from(`${kind}:${next}`).toString("base64url")}:{}}}async function dispatch(method,params,context){const visible=(items)=>items.filter((item)=>!item.available||item.available(context.principal));if(method==="initialize"){if(typeof params.protocolVersion!=="string"||!record(params.capabilities)||!record(params.clientInfo)||typeof params.clientInfo.name!=="string"||typeof params.clientInfo.version!=="string")invalid("Invalid initialization parameters");return{protocolVersion:MCP_PROTOCOL_VERSIONS.includes(params.protocolVersion)?params.protocolVersion:MCP_PROTOCOL_VERSIONS[0],capabilities:{tools:{listChanged:!1},resources:{subscribe:!1,listChanged:!1},prompts:{listChanged:!1}},serverInfo:{name:options.name,version:options.version},...options.instructions?{instructions:options.instructions}:{}}}if(method==="ping")return{};if(method==="tools/list")return page(visible(tools).map(({execute,available,...tool})=>tool),params,"tools");if(method==="tools/call"){if(typeof params.name!=="string"||params.arguments!==void 0&&!record(params.arguments))invalid("Send a tool name and object arguments");const tool=visible(tools).find((item)=>item.name===params.name);if(!tool)invalid("Unknown or unavailable tool");const args=params.arguments??{},errors=validateSchema(args,tool.inputSchema);if(errors.length)invalid(errors.slice(0,5).join("; "));try{context.signal.throwIfAborted();const result=await tool.execute(args,context);if(tool.outputSchema&&!result.isError){if(!result.structuredContent||validateSchema(result.structuredContent,tool.outputSchema).length)throw Error(`MCP output failed validation: ${tool.name}`)}return result}catch(error){options.onError?.(error,context);return{content:[{type:"text",text:"The operation failed. Try again or check the application."}],isError:!0}}}if(method==="resources/list")return page(visible(resources).map(({read,available,...resource})=>resource),params,"resources");if(method==="resources/templates/list")return{resourceTemplates:[]};if(method==="resources/read"){if(typeof params.uri!=="string")invalid("Send a resource URI");const resource=visible(resources).find((item)=>item.uri===params.uri);if(!resource)throw new ProtocolError(-32002,"Unknown or unavailable resource");return{contents:[{uri:resource.uri,mimeType:resource.mimeType??"text/plain",text:await resource.read(context)}]}}if(method==="prompts/list")return page(visible(prompts).map(({get,available,...prompt})=>prompt),params,"prompts");if(method==="prompts/get"){if(typeof params.name!=="string"||params.arguments!==void 0&&!record(params.arguments))invalid("Send a prompt name and string arguments");const prompt=visible(prompts).find((item)=>item.name===params.name);if(!prompt)invalid("Unknown or unavailable prompt");const args=params.arguments??{},declared=prompt.arguments??[];if(Object.entries(args).some(([key,value])=>typeof value!=="string"||value.length>16000||!declared.some((arg)=>arg.name===key)))invalid("Invalid prompt arguments");if(declared.some((arg)=>arg.required&&!args[arg.name]))invalid("Missing required prompt argument");return prompt.get(args,context)}throw new ProtocolError(-32601,"Method not found")}return{async handle(request){const origin=request.headers.get("origin");if(origin&&!options.allowedOrigins?.includes(origin))return failure(null,-32600,"Origin is not allowed",403);let principal;try{principal=await options.authenticate(request)}catch{return failure(null,-32603,"Authentication service failed",500)}if(principal instanceof Response)return principal;const finish=(response)=>{if(options.responseHeaders)new Headers(options.responseHeaders(principal)).forEach((value,key)=>response.headers.set(key,value));return response};if(request.method!=="POST")return finish(json({error:"Use POST for this stateless MCP endpoint"},405,{Allow:"POST"}));const version=request.headers.get("mcp-protocol-version");if(version&&!MCP_PROTOCOL_VERSIONS.includes(version))return finish(failure(null,-32600,"Unsupported MCP protocol version",400));if(!accepts(request.headers.get("accept"),"application/json")||!accepts(request.headers.get("accept"),"text/event-stream"))return finish(failure(null,-32600,"Accept must include application/json and text/event-stream",406));let message;try{message=await readJsonBody(request,maxBodyBytes)}catch(error){if(error instanceof RequestBodyError)return finish(failure(null,-32700,error.message,error.status));return finish(failure(null,-32603,"Could not read request body",500))}if(!record(message)||message.jsonrpc!=="2.0"||typeof message.method!=="string"||Object.hasOwn(message,"result")||Object.hasOwn(message,"error")||message.params!==void 0&&!record(message.params))return finish(failure(null,-32600,"Invalid JSON-RPC request",400));if(!Object.hasOwn(message,"id")){if(!message.method.startsWith("notifications/"))return finish(failure(null,-32600,"Requests require an id",400));return finish(new Response(null,{status:202,headers:{"Cache-Control":"no-store"}}))}const id=message.id;if(typeof id!=="string"&&(typeof id!=="number"||!Number.isSafeInteger(id)))return finish(failure(null,-32600,"Request id must be a string or safe integer",400));const context={principal,request,signal:request.signal};try{const result=await dispatch(message.method,message.params??{},context);return finish(json({jsonrpc:"2.0",id,result}))}catch(error){if(error instanceof ProtocolError)return finish(failure(id,error.code,error.message));options.onError?.(error,context);return finish(failure(id,-32603,"Internal error"))}}}}export function mcpTextResult(data,isError=!1){return{content:[{type:"text",text:JSON.stringify(data)}],structuredContent:data,...isError?{isError:!0}:{}}}
|
|
1
|
+
import{readJsonBody,RequestBodyError}from"@stacksjs/router/body";import{validateSchema}from"./schema";export const MCP_PROTOCOL_VERSIONS=["2025-11-25","2025-06-18","2025-03-26"];class ProtocolError extends Error{code;constructor(code,message){super(message);this.code=code}}const invalid=(message)=>{throw new ProtocolError(-32602,message)};function record(value){return value!==null&&typeof value==="object"&&!Array.isArray(value)}function json(body,status=200,headers){const response=Response.json(body,{status,headers});response.headers.set("Cache-Control","no-store");response.headers.set("X-Content-Type-Options","nosniff");return response}function failure(id,code,message,status=200){return json({jsonrpc:"2.0",id,error:{code,message}},status)}function accepts(header,type){return(header??"").split(",").some((part)=>{const[media,...params]=part.trim().toLowerCase().split(";"),quality=params.find((param)=>param.trim().startsWith("q="))?.trim().slice(2);return media?.trim()===type&&(quality===void 0||Number(quality)>0&&Number(quality)<=1)})}function checkSchema(schema){const supported=new Set(["$schema","type","description","title","default","examples","enum","const","properties","required","additionalProperties","items","minimum","maximum","minLength","maxLength","minItems","maxItems","pattern"]);for(const key of Object.keys(schema))if(!supported.has(key))throw TypeError(`Unsupported MCP schema keyword: ${key}; use an execute validator for domain rules`);const types=["object","array","string","number","integer","boolean","null"];if(schema.type!==void 0&&!(typeof schema.type==="string"?types.includes(schema.type):Array.isArray(schema.type)&&schema.type.length>0&&schema.type.every((type)=>types.includes(type))))throw TypeError("Invalid MCP schema type");if(schema.required!==void 0&&(!Array.isArray(schema.required)||!schema.required.every((key)=>typeof key==="string")||new Set(schema.required).size!==schema.required.length))throw TypeError("MCP required must contain unique property names");if(schema.properties!==void 0&&!record(schema.properties))throw TypeError("MCP properties must be an object");const primitive=(value)=>value===null||["string","boolean"].includes(typeof value)||typeof value==="number"&&Number.isFinite(value);if(Object.hasOwn(schema,"const")&&!primitive(schema.const))throw TypeError("MCP const supports primitive values only");if(schema.enum!==void 0&&(!Array.isArray(schema.enum)||!schema.enum.length||!schema.enum.every(primitive)))throw TypeError("MCP enum supports a non-empty array of primitive values");for(const key of["minimum","maximum","minLength","maxLength","minItems","maxItems"])if(schema[key]!==void 0&&(typeof schema[key]!=="number"||!Number.isFinite(schema[key])||!["minimum","maximum"].includes(key)&&(!Number.isSafeInteger(schema[key])||Number(schema[key])<0)))throw TypeError(`Invalid MCP schema bound: ${key}`);for(const[min,max]of[["minimum","maximum"],["minLength","maxLength"],["minItems","maxItems"]])if(schema[min]!==void 0&&schema[max]!==void 0&&Number(schema[min])>Number(schema[max]))throw TypeError("MCP schema bounds are reversed");if(schema.pattern!==void 0&&typeof schema.pattern!=="string")throw TypeError("MCP pattern must be a string");if(schema.additionalProperties!==void 0&&typeof schema.additionalProperties!=="boolean")throw TypeError("MCP additionalProperties must be a boolean");if(schema.pattern!==void 0)new RegExp(String(schema.pattern));if(record(schema.properties))for(const child of Object.values(schema.properties)){if(!record(child))throw TypeError("MCP property schemas must be objects");checkSchema(child)}if(schema.items!==void 0){if(!record(schema.items))throw TypeError("MCP items must be an object schema");checkSchema(schema.items)}}export function createMCPServer(options){options={...options};const tools=(options.tools??[]).map((tool)=>({...tool,inputSchema:structuredClone(tool.inputSchema),...tool.outputSchema?{outputSchema:structuredClone(tool.outputSchema)}:{},...tool.annotations?{annotations:{...tool.annotations}}:{},...tool.securitySchemes?{securitySchemes:structuredClone(tool.securitySchemes)}:{},...tool._meta?{_meta:structuredClone(tool._meta)}:{}})),resources=(options.resources??[]).map((resource)=>({...resource})),prompts=(options.prompts??[]).map((prompt)=>({...prompt,arguments:prompt.arguments?.map((arg)=>({...arg}))})),pageSize=options.pageSize??50,maxBodyBytes=options.maxBodyBytes??1048576;if(!Number.isSafeInteger(pageSize)||pageSize<1||pageSize>1000)throw RangeError("MCP pageSize must be between 1 and 1000");if(!Number.isSafeInteger(maxBodyBytes)||maxBodyBytes<1)throw RangeError("MCP maxBodyBytes must be positive");for(const list of[tools,prompts]){const names=new Set;for(const item of list){if(!/^[\w.-]{1,128}$/.test(item.name)||names.has(item.name))throw TypeError(`Invalid or duplicate MCP name: ${item.name}`);names.add(item.name)}}const origins=[...options.allowedOrigins??[]],resourceMetadataUrl=options.resourceMetadataUrl;if(resourceMetadataUrl){const metadata=new URL(resourceMetadataUrl);if(metadata.protocol!=="https:"&&!(metadata.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(metadata.hostname))||metadata.username||metadata.password||/[\"\\\r\n]/.test(resourceMetadataUrl))throw TypeError("Invalid MCP resourceMetadataUrl")}const reportError=(error,context)=>{try{Promise.resolve(options.onError?.(error,context)).catch(()=>{})}catch{}};for(const origin of origins)if(origin==="null"||new URL(origin).origin!==origin||!/^https?:/.test(origin))throw TypeError("MCP allowedOrigins must contain exact HTTP(S) origins");if(new Set(resources.map((item)=>item.uri)).size!==resources.length)throw TypeError("Duplicate MCP resource URI");for(const tool of tools){if(tool.inputSchema.type!=="object")throw TypeError("MCP tool inputSchema must be an object schema");checkSchema(tool.inputSchema);if(tool.outputSchema){if(tool.outputSchema.type!=="object")throw TypeError("MCP tool outputSchema must be an object schema");checkSchema(tool.outputSchema)}}function page(items,params,kind){let offset=0;if(params.cursor!==void 0){if(typeof params.cursor!=="string")invalid("Invalid pagination cursor");const match=Buffer.from(params.cursor,"base64url").toString().match(new RegExp(`^${kind}:(\\d+)$`));if(!match)invalid("Invalid pagination cursor");offset=Number(match[1]);if(!Number.isSafeInteger(offset)||offset<1||offset>=items.length||offset%pageSize!==0)invalid("Invalid pagination cursor")}const next=offset+pageSize;return{[kind]:items.slice(offset,next),...next<items.length?{nextCursor:Buffer.from(`${kind}:${next}`).toString("base64url")}:{}}}async function dispatch(method,params,context){const visible=(items)=>items.filter((item)=>!item.available||item.available(context.principal));if(method==="initialize"){if(typeof params.protocolVersion!=="string"||!record(params.capabilities)||!record(params.clientInfo)||typeof params.clientInfo.name!=="string"||typeof params.clientInfo.version!=="string")invalid("Invalid initialization parameters");return{protocolVersion:MCP_PROTOCOL_VERSIONS.includes(params.protocolVersion)?params.protocolVersion:MCP_PROTOCOL_VERSIONS[0],capabilities:{tools:{listChanged:!1},resources:{subscribe:!1,listChanged:!1},prompts:{listChanged:!1}},serverInfo:{name:options.name,version:options.version},...options.instructions?{instructions:options.instructions}:{}}}if(method==="ping")return{};if(method==="tools/list")return page(visible(tools).map(({execute,available,...tool})=>tool),params,"tools");if(method==="tools/call"){if(typeof params.name!=="string"||params.arguments!==void 0&&!record(params.arguments))invalid("Send a tool name and object arguments");const tool=visible(tools).find((item)=>item.name===params.name);if(!tool)invalid("Unknown or unavailable tool");const args=params.arguments??{},errors=validateSchema(args,tool.inputSchema);if(errors.length)invalid(errors.slice(0,5).join("; "));try{context.signal.throwIfAborted();const result=await tool.execute(args,context);if(tool.outputSchema&&!result.isError){if(!result.structuredContent||validateSchema(result.structuredContent,tool.outputSchema).length)throw Error(`MCP output failed validation: ${tool.name}`)}return result}catch(error){reportError(error,context);return{content:[{type:"text",text:"The operation failed. Try again or check the application."}],isError:!0}}}if(method==="resources/list")return page(visible(resources).map(({read,available,...resource})=>resource),params,"resources");if(method==="resources/templates/list")return{resourceTemplates:[]};if(method==="resources/read"){if(typeof params.uri!=="string")invalid("Send a resource URI");const resource=visible(resources).find((item)=>item.uri===params.uri);if(!resource)throw new ProtocolError(-32002,"Unknown or unavailable resource");return{contents:[{uri:resource.uri,mimeType:resource.mimeType??"text/plain",text:await resource.read(context)}]}}if(method==="prompts/list")return page(visible(prompts).map(({get,available,...prompt})=>prompt),params,"prompts");if(method==="prompts/get"){if(typeof params.name!=="string"||params.arguments!==void 0&&!record(params.arguments))invalid("Send a prompt name and string arguments");const prompt=visible(prompts).find((item)=>item.name===params.name);if(!prompt)invalid("Unknown or unavailable prompt");const args=params.arguments??{},declared=prompt.arguments??[];if(Object.entries(args).some(([key,value])=>typeof value!=="string"||value.length>16000||!declared.some((arg)=>arg.name===key)))invalid("Invalid prompt arguments");if(declared.some((arg)=>arg.required&&!args[arg.name]))invalid("Missing required prompt argument");return prompt.get(args,context)}throw new ProtocolError(-32601,"Method not found")}return{async handle(request){const origin=request.headers.get("origin");if(origin&&!origins.includes(origin))return failure(null,-32600,"Origin is not allowed",403);let principal;try{principal=await options.authenticate(request)}catch{return failure(null,-32603,"Authentication service failed",500)}if(principal instanceof Response){if((principal.status===401||principal.status===403)&&resourceMetadataUrl&&/^Bearer(?:\s|$)/i.test(principal.headers.get("WWW-Authenticate")??"Bearer")){const headers=new Headers(principal.headers),challenge=headers.get("WWW-Authenticate")??"Bearer";headers.set("WWW-Authenticate",`${challenge}${/^Bearer$/i.test(challenge.trim())?" ":", "}resource_metadata="${resourceMetadataUrl}"`);return new Response(principal.body,{status:principal.status,headers})}return principal}const finish=(response)=>{if(options.responseHeaders)try{new Headers(options.responseHeaders(principal)).forEach((value,key)=>response.headers.set(key,value))}catch(error){reportError(error,{principal,request,signal:request.signal})}return response};if(request.method!=="POST")return finish(json({error:"Use POST for this stateless MCP endpoint"},405,{Allow:"POST"}));const version=request.headers.get("mcp-protocol-version");if(version&&!MCP_PROTOCOL_VERSIONS.includes(version))return finish(failure(null,-32600,"Unsupported MCP protocol version",400));if(!accepts(request.headers.get("accept"),"application/json")||!accepts(request.headers.get("accept"),"text/event-stream"))return finish(failure(null,-32600,"Accept must include application/json and text/event-stream",406));let message;try{message=await readJsonBody(request,maxBodyBytes)}catch(error){if(error instanceof RequestBodyError)return finish(failure(null,-32700,error.message,error.status));return finish(failure(null,-32603,"Could not read request body",500))}if(!record(message)||message.jsonrpc!=="2.0"||typeof message.method!=="string"||Object.hasOwn(message,"result")||Object.hasOwn(message,"error")||message.params!==void 0&&!record(message.params))return finish(failure(null,-32600,"Invalid JSON-RPC request",400));if(!Object.hasOwn(message,"id")){if(!message.method.startsWith("notifications/"))return finish(failure(null,-32600,"Requests require an id",400));return finish(new Response(null,{status:202,headers:{"Cache-Control":"no-store"}}))}const id=message.id;if(typeof id!=="string"&&(typeof id!=="number"||!Number.isSafeInteger(id)))return finish(failure(null,-32600,"Request id must be a string or safe integer",400));const context={principal,request,signal:request.signal};try{const result=await dispatch(message.method,message.params??{},context);return finish(json({jsonrpc:"2.0",id,result}))}catch(error){if(error instanceof ProtocolError)return finish(failure(id,error.code,error.message));reportError(error,context);return finish(failure(id,-32603,"Internal error"))}}}}export function mcpTextResult(data,isError=!1){return{content:[{type:"text",text:JSON.stringify(data)}],structuredContent:data,...isError?{isError:!0}:{}}}
|
package/dist/schema.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
function typeMatches(value,type){if(type==="array")return Array.isArray(value);if(type==="integer")return typeof value==="number"&&Number.isInteger(value);if(type==="object")return typeof value==="object"&&value!==null&&!Array.isArray(value);if(type==="null")return value===null;return typeof value===type&&(type!=="number"||Number.isFinite(value))}export function validateSchema(value,schema,path="$"){const errors=[];if(schema.type){const allowed=Array.isArray(schema.type)?schema.type:[schema.type];if(!allowed.some((type)=>typeMatches(value,type)))return[`${path} must be ${allowed.join(" or ")}`]}if(Object.hasOwn(schema,"const")&&!Object.is(schema.const,value))errors.push(`${path} must equal ${String(schema.const)}`);if(schema.enum&&!schema.enum.some((candidate)=>Object.is(candidate,value)))errors.push(`${path} must be one of ${schema.enum.join(", ")}`);if(typeof value==="string"){if(schema.pattern&&!new RegExp(schema.pattern).test(value))errors.push(`${path} does not match the required pattern`);if(schema.minLength!==void 0&&
|
|
1
|
+
function typeMatches(value,type){if(type==="array")return Array.isArray(value);if(type==="integer")return typeof value==="number"&&Number.isInteger(value);if(type==="object")return typeof value==="object"&&value!==null&&!Array.isArray(value);if(type==="null")return value===null;return typeof value===type&&(type!=="number"||Number.isFinite(value))}export function validateSchema(value,schema,path="$"){const errors=[];if(schema.type){const allowed=Array.isArray(schema.type)?schema.type:[schema.type];if(!allowed.some((type)=>typeMatches(value,type)))return[`${path} must be ${allowed.join(" or ")}`]}if(Object.hasOwn(schema,"const")&&!Object.is(schema.const,value))errors.push(`${path} must equal ${String(schema.const)}`);if(schema.enum&&!schema.enum.some((candidate)=>Object.is(candidate,value)))errors.push(`${path} must be one of ${schema.enum.join(", ")}`);if(typeof value==="string"){const length=[...value].length;if(schema.pattern&&!new RegExp(schema.pattern).test(value))errors.push(`${path} does not match the required pattern`);if(schema.minLength!==void 0&&length<schema.minLength)errors.push(`${path} must contain at least ${schema.minLength} characters`);if(schema.maxLength!==void 0&&length>schema.maxLength)errors.push(`${path} must contain at most ${schema.maxLength} characters`)}if(typeof value==="number"){if(schema.minimum!==void 0&&value<schema.minimum)errors.push(`${path} must be at least ${schema.minimum}`);if(schema.maximum!==void 0&&value>schema.maximum)errors.push(`${path} must be at most ${schema.maximum}`)}if(Array.isArray(value)){if(schema.minItems!==void 0&&value.length<schema.minItems)errors.push(`${path} must contain at least ${schema.minItems} items`);if(schema.maxItems!==void 0&&value.length>schema.maxItems)errors.push(`${path} must contain at most ${schema.maxItems} items`);if(schema.items)value.forEach((item,index)=>errors.push(...validateSchema(item,schema.items,`${path}[${index}]`)))}if(typeof value==="object"&&value!==null&&!Array.isArray(value)){const record=value;for(const key of schema.required??[])if(!Object.hasOwn(record,key))errors.push(`${path}.${key} is required`);for(const[key,child]of Object.entries(schema.properties??{}))if(Object.hasOwn(record,key))errors.push(...validateSchema(record[key],child,`${path}.${key}`));if(schema.additionalProperties===!1){const known=new Set(Object.keys(schema.properties??{}));for(const key of Object.keys(record))if(!known.has(key))errors.push(`${path}.${key} is not allowed`)}}return errors}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@stacksjs/ai",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"sideEffects": false,
|
|
5
|
-
"version": "0.75.
|
|
5
|
+
"version": "0.75.193",
|
|
6
6
|
"description": "Stacks Artificial Intelligence.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -65,10 +65,10 @@
|
|
|
65
65
|
"prepublishOnly": "bun run build"
|
|
66
66
|
},
|
|
67
67
|
"dependencies": {
|
|
68
|
-
"@stacksjs/cli": "0.75.
|
|
69
|
-
"@stacksjs/config": "0.75.
|
|
70
|
-
"@stacksjs/router": "0.75.
|
|
71
|
-
"@stacksjs/storage": "0.75.
|
|
68
|
+
"@stacksjs/cli": "0.75.193",
|
|
69
|
+
"@stacksjs/config": "0.75.193",
|
|
70
|
+
"@stacksjs/router": "0.75.193",
|
|
71
|
+
"@stacksjs/storage": "0.75.193",
|
|
72
72
|
"@stacksjs/ts-cloud": "^0.16.56"
|
|
73
73
|
},
|
|
74
74
|
"peerDependencies": {
|