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