razeri_plugin_api/lib.rs
1// Copyright (C) 2026 Gabriel Shea
2// Licensed under either of the Apache License, Version 2.0 or the MIT
3// license, at your option.
4
5#![warn(missing_docs)]
6
7//! # razeri-plugin-api
8//!
9//! Extension point traits and registries for the Razeri platform.
10//!
11//! This crate defines the **plugin API surface** — the traits that third-party
12//! developers implement to extend Razeri with new database dialects, storage
13//! backends, LLM providers, language renderers, and event sinks.
14//!
15//! ## Extension Points
16//!
17//! | Trait | Registry | Key Type |
18//! |-------|----------|----------|
19//! | [`PlatformDialect`] | [`PlatformRegistry`] | string (canonical name) |
20//! | [`StorageProviderFactory`] | [`StorageRegistry`] | `StoragePlatform` enum + string |
21//! | [`LlmClientFactory`] / [`LlmClient`] | [`LlmRegistry`] | string |
22//! | [`LlmModelAdapter`] | [`ModelRegistry`] | (provider, model) pair |
23//! | [`LanguageRendererFactory`] | [`LanguageRegistry`] | string |
24//! | [`EventSink`] | [`EventRegistry`] | fan-out (all sinks) |
25//!
26//! ## Composition Root
27//!
28//! Use [`PluginRegistryBuilder`] to wire everything together at application
29//! startup, then pass the resulting [`PluginRegistry`] to subsystems:
30//!
31//! ```ignore
32//! let registry = PluginRegistryBuilder::new()
33//! .with_dialect("postgres", Arc::new(pg_dialect))
34//! .with_llm("ollama".into(), Arc::new(ollama_factory))
35//! .with_auto_discovered()
36//! .build();
37//! ```
38
39pub mod alerting;
40pub mod builder;
41pub mod conformance;
42pub mod dialect;
43pub mod dynamic;
44pub mod error;
45pub mod event;
46pub mod language;
47pub mod llm;
48pub mod model;
49pub mod registry;
50pub mod resolve;
51pub mod sinks;
52pub mod storage;
53
54// ── Convenience re-exports ────────────────────────────────────────────
55
56pub use builder::PluginRegistryBuilder;
57pub use dialect::{DialectInfo, PlatformDialect, PlatformRegistry};
58
59// The contract a plugin's types and traits are written against, re-exported so
60// a dialect crate can depend on this one crate and reach everything it needs.
61pub use dynamic::{
62 API_VERSION, ApiVersionFn, PLUGIN_API_VERSION_SYMBOL, PLUGIN_REGISTER_SYMBOL, PluginLoadResult,
63 RegisterPluginFn,
64};
65pub use razeri_plugin_contract;
66
67#[cfg(all(feature = "dynamic", not(target_arch = "wasm32")))]
68pub use dynamic::load_plugins_from_dir;
69pub use error::{EventSinkError, LanguageError, LlmError, StorageError};
70pub use event::{
71 EventCategory, EventEmitter, EventFilterKeys, EventPayload, EventQuery, EventRegistry,
72 EventSeverity, EventSink, EventSubject, EventSubjectKind, EventSummary, EventValidationError,
73 RazeriEvent, WorkflowContext,
74};
75pub use language::{LanguageRegistry, LanguageRenderer, LanguageRendererFactory};
76pub use llm::{
77 ChatResponse, LlmClient, LlmClientFactory, LlmRegistry, Message, ModelInfo, TokenUsage,
78};
79pub use model::{LlmModelAdapter, ModelCapabilities, ModelRegistry};
80pub use registry::PluginRegistry;
81pub use resolve::ResolveError;
82pub use storage::{
83 StorageListEntry, StorageMetadata, StorageOps, StorageProviderFactory, StorageRegistry,
84};
85
86// Re-export built-in sink types and factory. The JSONL sink is native-only
87// (filesystem via `tokio::fs`), so its re-export is absent on wasm.
88#[cfg(not(target_arch = "wasm32"))]
89pub use sinks::jsonl::JsonlSink;
90pub use sinks::stdout::StdoutSink;
91pub use sinks::webhook::WebhookSink;
92pub use sinks::{EventSinkConfig, create_sink};
93
94// Re-export alerting and health model
95pub use alerting::{
96 AlertCondition, AlertMetric, AlertResult, AlertRule, AlertScope, ComparisonOp, HealthDomain,
97 HealthStatus, HealthSummary, SinkStatus, discover_health, evaluate_rules, health_for_domain,
98};
99
100// Re-export inventory registration types
101pub use builder::{
102 DialectRegistration, EventSinkRegistration, LanguageRegistration, LlmRegistration,
103 ModelRegistration, StorageRegistration,
104};
105
106// ── Convenience helpers ───────────────────────────────────────────────
107
108use std::sync::Arc;
109
110/// Resolve a platform name to the dialect that renders it.
111///
112/// This is the **one resolver** — all service crates call it instead of
113/// matching on names themselves. Each dialect declares the spellings it answers
114/// to, so `"pg"` reaches `PostgreSQL` and `"mssql"` reaches SQL Server because
115/// those dialects say so. Matching ignores case and surrounding whitespace, and
116/// an unknown name comes back with near-miss suggestions.
117///
118/// A dialect the host loaded at run time resolves the same way as one linked
119/// in: both are read off the registry.
120///
121/// # Errors
122///
123/// [`ResolveError::UnknownDialect`], with suggestions, if no loaded dialect
124/// answers to the name; [`ResolveError::AmbiguousName`] if two do.
125///
126/// # Examples
127///
128/// ```no_run
129/// use razeri_plugin_api::{ResolveError, resolve_dialect_by_name};
130///
131/// # fn main() -> Result<(), ResolveError> {
132/// // Aliases, canonical names and casing all resolve to the same dialect.
133/// let dialect = resolve_dialect_by_name("pg")?;
134/// println!("{}", dialect.info().name);
135///
136/// let dialect = resolve_dialect_by_name("SNOWFLAKE")?;
137/// let dialect = resolve_dialect_by_name("sqlite")?;
138/// # Ok(())
139/// # }
140/// ```
141pub fn resolve_dialect_by_name(name: &str) -> Result<Arc<dyn PlatformDialect>, ResolveError> {
142 resolve::resolve_dialect_by_name(name)
143}
144
145/// Resolve a dialect by string name, returning `None` on failure.
146///
147/// Convenience wrapper around [`resolve_dialect_by_name`] for callers that
148/// prefer `Option` over `Result`.
149#[must_use]
150pub fn try_resolve_dialect_by_name(name: &str) -> Option<Arc<dyn PlatformDialect>> {
151 resolve_dialect_by_name(name).ok()
152}