SPB Git
6.0 KB · 116 lines typescript
1import { z } from "zod";23/** Controlled vocabularies (keep in sync with docs/data-model.md). */4export const PRODUCT_TYPES = ["application", "library", "framework", "api-service", "platform", "cli-tool", "language", "database", "model", "data-service", "extension", "specification", "service"] as const;5export const PRODUCT_STATUSES = ["active", "maintenance", "deprecated", "discontinued", "renamed", "unknown"] as const;6export const SOURCE_MODELS = ["open-source", "proprietary", "source-available", "mixed"] as const;7export const PRICING_MODELS = ["free", "freemium", "paid", "enterprise", "usage-based", "open-source", "academic", "unknown"] as const;8export const OPERATING_SYSTEMS = ["macos", "windows", "linux", "ios", "android", "web", "any"] as const;9export const EXECUTION_MODES = ["cloud", "self-hosted", "local"] as const;10export const INTERFACES = ["web", "desktop", "mobile", "cli", "api", "sdk", "library", "ide-extension", "browser-extension", "mcp-server"] as const;11export const DEPLOYMENT_MODELS = ["saas", "on-premises", "hybrid", "desktop", "embedded", "managed-cloud"] as const;12export const SOURCE_TYPES = ["official_site", "official_docs", "repository", "release_notes", "pricing_page", "policy_page", "license_page", "package_registry", "academic_paper", "third_party_directory", "news", "other"] as const;13export const CONFIDENCE = ["high", "medium", "low"] as const;14export const DEPTHS = ["deep", "standard"] as const;15export const EDITORIAL_STATUSES = ["draft", "reviewed", "published", "deprecated"] as const;16export const VISIBILITIES = ["public", "private"] as const;17export const USE_STATES = ["active", "testing", "candidate", "archived", "rejected"] as const;1819const slug = z.string().regex(/^[a-z0-9]+(?:-[a-z0-9]+)*$/, "slug must be kebab-case");20const url = z.string().url().refine((u) => /^https?:\/\//.test(u), "http(s) only");21const date = z.string().regex(/^\d{4}-\d{2}-\d{2}$/, "YYYY-MM-DD");22const shortList = (max = 12) => z.array(z.string().min(1).max(160)).max(max);2324export const sourceSchema = z.object({25  url,26  type: z.enum(SOURCE_TYPES),27  title: z.string().max(200).optional(),28  /** Which record fields this source supports (e.g. ["license_spdx","pricing_model"]). */29  supports: z.array(z.string()).default([]),30  checked_at: date,31  note: z.string().max(400).optional(),32});3334export const toolRecordSchema = z.object({35  slug,36  name: z.string().min(1).max(120),37  aliases: z.array(z.string().max(120)).default([]),38  vendor: z.object({ name: z.string().min(1).max(120), slug: slug.optional(), website: url.optional() }),39  homepage_url: url,40  docs_url: url.optional(),41  repo_url: url.optional(),42  pricing_url: url.optional(),43  license_url: url.optional(),44  privacy_url: url.optional(),45  security_url: url.optional(),46  logo_url: url.optional(),47  logo_attribution: z.string().max(200).optional(),48  product_type: z.enum(PRODUCT_TYPES),49  product_status: z.enum(PRODUCT_STATUSES),50  summary: z.string().min(40).max(220),51  /** Original editorial description in Markdown. deep: 100–300 words; standard: ≥ 40 words. */52  description: z.string().min(150),53  capabilities: shortList(10).min(2),54  workflows: shortList(8).default([]),55  advantages: shortList(8).default([]),56  limitations: shortList(8).default([]),57  prerequisites: shortList(8).default([]),58  audiences: shortList(8).default([]),59  alternatives: z.array(slug).max(10).default([]),60  comparison_notes: z.string().max(1200).optional(),61  categories: z.array(z.object({ slug, primary: z.boolean().default(false), label: z.string().max(80).optional() })).min(1).max(5),62  tags: z.array(slug).max(20).default([]),63  use_cases: z.array(slug).max(12).default([]),64  operating_systems: z.array(z.enum(OPERATING_SYSTEMS)).default([]),65  execution_modes: z.array(z.enum(EXECUTION_MODES)).min(1),66  interfaces: z.array(z.enum(INTERFACES)).min(1),67  deployment_models: z.array(z.enum(DEPLOYMENT_MODELS)).default([]),68  integrations: shortList(20).default([]),69  data_formats: shortList(20).default([]),70  languages: shortList(20).default([]),71  source_model: z.enum(SOURCE_MODELS),72  license_spdx: z.string().max(60).optional(),73  pricing_model: z.array(z.enum(PRICING_MODELS)).min(1),74  pricing_checked_at: date.optional(),75  privacy_posture: z.string().max(600).optional(),76  data_residency: z.string().max(300).optional(),77  self_hostable: z.boolean().nullable().default(null),78  sources: z.array(sourceSchema).min(1),79  confidence: z.enum(CONFIDENCE),80  uncertainty_note: z.string().max(600).optional(),81  discovery_method: z.string().max(80).default("editorial-research"),82  depth: z.enum(DEPTHS).default("standard"),83  editorial_status: z.enum(EDITORIAL_STATUSES).default("reviewed"),84  visibility: z.enum(VISIBILITIES).default("public"),85  featured: z.boolean().default(false),86  author: z.string().max(80).default("editorial"),87  reviewed_at: date.optional(),88  reviewed_by: z.string().max(80).optional(),89});90export type ToolRecord = z.infer<typeof toolRecordSchema>;91export type ToolRecordInput = z.input<typeof toolRecordSchema>;9293export const categorySchema: z.ZodType<{ slug: string; name: string; description: string; children?: any[] }> = z.object({94  slug, name: z.string(), description: z.string(), children: z.array(z.lazy(() => categorySchema)).optional(),95});9697export const collectionSchema = z.object({98  slug, title: z.string().min(3).max(120), description: z.string().min(40).max(1200), published: z.boolean().default(true),99  items: z.array(z.object({ tool: slug, blurb: z.string().max(400).optional() })).min(2),100});101102export const ownerRelevanceSchema = z.object({103  tool: slug,104  use_state: z.enum(USE_STATES),105  rating: z.number().int().min(1).max(5).optional(),106  notes: z.string().max(4000).optional(),107  cluster_compat: z.enum(["yes", "partial", "no", "unknown"]).default("unknown"),108  priority: z.enum(["high", "medium", "low"]).default("medium"),109  projects: z.array(slug).default([]),110  subscription_status: z.string().max(120).optional(),111  custom_tags: z.array(slug).default([]),112  review_at: date.optional(),113});114115export const WORD = (s: string) => s.trim().split(/\s+/).filter(Boolean).length;116
Files · main
Menu

Theme