From 1a86036acec211cd3ef34395e0406adccb184d8f Mon Sep 17 00:00:00 2001 From: Ajax Davis Date: Fri, 28 Nov 2025 02:12:11 +1000 Subject: [PATCH] feat(types): add comprehensive TPMJS field schemas with validation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add @tpmjs/types/tpmjs module with minimal and rich tier schemas - Define 12 valid tool categories (web-scraping, api-integration, etc.) - Add schemas for parameters, returns, authentication, pricing, links - Implement validateTpmjsField() with automatic tier detection - Add type guards (isTpmjsMinimal, isTpmjsRich) - Support optional rich fields: frameworks, aiAgent, status, tags Validation features: - Minimal tier: category, description (20-500 chars), example (10+ chars) - Rich tier: adds parameters, returns, auth, pricing, frameworks, etc. - Auto-detects tier based on presence of rich fields - Comprehensive error messages with Zod Export configuration: - Added ./tpmjs export to package.json - Updated tsup.config.ts entry points - Built and type-checked successfully 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- packages/types/package.json | 4 + packages/types/src/tpmjs.ts | 200 ++++++++++++++++++++++++++++++++++ packages/types/tsup.config.ts | 2 +- 3 files changed, 205 insertions(+), 1 deletion(-) create mode 100644 packages/types/src/tpmjs.ts diff --git a/packages/types/package.json b/packages/types/package.json index b1e9b2a..d26643e 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -10,6 +10,10 @@ "./registry": { "types": "./dist/registry.d.ts", "default": "./dist/registry.js" + }, + "./tpmjs": { + "types": "./dist/tpmjs.d.ts", + "default": "./dist/tpmjs.js" } }, "files": ["dist"], diff --git a/packages/types/src/tpmjs.ts b/packages/types/src/tpmjs.ts new file mode 100644 index 0000000..5bb4f2b --- /dev/null +++ b/packages/types/src/tpmjs.ts @@ -0,0 +1,200 @@ +import { z } from 'zod'; + +/** + * Valid tool categories for TPMJS registry + */ +export const TPMJS_CATEGORIES = [ + 'web-scraping', + 'data-processing', + 'file-operations', + 'communication', + 'database', + 'api-integration', + 'image-processing', + 'text-analysis', + 'automation', + 'ai-ml', + 'security', + 'monitoring', +] as const; + +export type TpmjsCategory = (typeof TPMJS_CATEGORIES)[number]; + +/** + * Tool parameter schema + */ +export const TpmjsParameterSchema = z.object({ + name: z.string().min(1), + type: z.string().min(1), + description: z.string().min(1), + required: z.boolean().default(false), + default: z.unknown().optional(), +}); + +export type TpmjsParameter = z.infer; + +/** + * Return value schema + */ +export const TpmjsReturnsSchema = z.object({ + type: z.string().min(1), + description: z.string().min(1), +}); + +export type TpmjsReturns = z.infer; + +/** + * Authentication configuration schema + */ +export const TpmjsAuthenticationSchema = z.object({ + required: z.boolean(), + type: z.enum(['api-key', 'oauth', 'basic-auth', 'custom']), + envVar: z.string().optional(), + docsUrl: z.string().url().optional(), +}); + +export type TpmjsAuthentication = z.infer; + +/** + * Pricing information schema + */ +export const TpmjsPricingSchema = z.object({ + model: z.enum(['free', 'freemium', 'paid', 'enterprise']), + freeLimit: z.string().optional(), + paidUrl: z.string().url().optional(), +}); + +export type TpmjsPricing = z.infer; + +/** + * External links schema + */ +export const TpmjsLinksSchema = z.object({ + documentation: z.string().url().optional(), + playground: z.string().url().optional(), + repository: z.string().url().optional(), + homepage: z.string().url().optional(), +}); + +export type TpmjsLinks = z.infer; + +/** + * AI Agent guidance schema + */ +export const TpmjsAiAgentSchema = z.object({ + useCase: z.string().min(10), + limitations: z.string().optional(), + examples: z.array(z.string()).optional(), +}); + +export type TpmjsAiAgent = z.infer; + +/** + * Minimal tier schema - required fields only + * This is the minimum required to publish a tool to TPMJS + */ +export const TpmjsMinimalSchema = z.object({ + category: z.enum(TPMJS_CATEGORIES, { + errorMap: () => ({ + message: `Category must be one of: ${TPMJS_CATEGORIES.join(', ')}`, + }), + }), + description: z.string().min(20, 'Description must be at least 20 characters').max(500), + example: z.string().min(10, 'Example must be at least 10 characters'), +}); + +export type TpmjsMinimal = z.infer; + +/** + * Rich tier schema - includes optional enhanced metadata + * Tools with these fields get better visibility and quality scores + */ +export const TpmjsRichSchema = TpmjsMinimalSchema.extend({ + parameters: z.array(TpmjsParameterSchema).optional(), + returns: TpmjsReturnsSchema.optional(), + authentication: TpmjsAuthenticationSchema.optional(), + pricing: TpmjsPricingSchema.optional(), + frameworks: z + .array(z.enum(['vercel-ai', 'langchain', 'llamaindex', 'haystack', 'semantic-kernel'])) + .optional(), + links: TpmjsLinksSchema.optional(), + tags: z.array(z.string().min(2).max(30)).max(10).optional(), + status: z.enum(['experimental', 'beta', 'stable', 'deprecated']).optional(), + aiAgent: TpmjsAiAgentSchema.optional(), +}); + +export type TpmjsRich = z.infer; + +/** + * Union type for either tier + */ +export type TpmjsField = TpmjsMinimal | TpmjsRich; + +/** + * Validation result type + */ +export interface ValidationResult { + valid: boolean; + tier: 'minimal' | 'rich' | null; + data?: TpmjsField; + errors?: z.ZodError; +} + +/** + * Validates a tpmjs field and determines its tier + */ +export function validateTpmjsField(tpmjs: unknown): ValidationResult { + // Try rich tier first + const richResult = TpmjsRichSchema.safeParse(tpmjs); + if (richResult.success) { + // Check if it has any rich-tier fields + const data = richResult.data; + const hasRichFields = + data.parameters || + data.returns || + data.authentication || + data.pricing || + data.frameworks || + data.links || + data.tags || + data.status || + data.aiAgent; + + return { + valid: true, + tier: hasRichFields ? 'rich' : 'minimal', + data: richResult.data, + }; + } + + // Try minimal tier + const minimalResult = TpmjsMinimalSchema.safeParse(tpmjs); + if (minimalResult.success) { + return { + valid: true, + tier: 'minimal', + data: minimalResult.data, + }; + } + + // Invalid + return { + valid: false, + tier: null, + errors: minimalResult.error, + }; +} + +/** + * Type guard for minimal tier + */ +export function isTpmjsMinimal(tpmjs: unknown): tpmjs is TpmjsMinimal { + return TpmjsMinimalSchema.safeParse(tpmjs).success; +} + +/** + * Type guard for rich tier + */ +export function isTpmjsRich(tpmjs: unknown): tpmjs is TpmjsRich { + return TpmjsRichSchema.safeParse(tpmjs).success; +} diff --git a/packages/types/tsup.config.ts b/packages/types/tsup.config.ts index 03d66e6..65d8c6c 100644 --- a/packages/types/tsup.config.ts +++ b/packages/types/tsup.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'tsup'; export default defineConfig({ - entry: ['src/tool.ts', 'src/registry.ts'], + entry: ['src/tool.ts', 'src/registry.ts', 'src/tpmjs.ts'], format: ['esm'], dts: true, clean: true,