# razeri-plugin-api
Extension traits and registries for building [Razeri](https://github.com/gaoash/razeri-releases) plugins.
This is the **single crate** third-party authors depend on to extend Razeri with new database dialects, storage backends, LLM providers, AI model adapters, language renderers, and event sinks.
## Extension Points
| `PlatformDialect` | `PlatformRegistry` | SQL dialect for a new database |
| `StorageProviderFactory` | `StorageRegistry` | Storage backend (S3, Azure, etc.) |
| `LlmClientFactory` | `LlmRegistry` | LLM provider (Ollama, OpenAI, etc.) |
| `LlmModelAdapter` | `ModelRegistry` | Model capabilities and routing |
| `LanguageRendererFactory` | `LanguageRegistry` | Code generation target language |
| `EventSink` | `EventRegistry` | Structured telemetry sink |
The rendering traits themselves live in
[`razeri-plugin-contract`](https://crates.io/crates/razeri-plugin-contract),
which this crate depends on and re-exports as `razeri_plugin_api::razeri_plugin_contract`.
## Quick Start
```rust
use std::sync::Arc;
use razeri_plugin_api::EngineVersion;
use razeri_plugin_api::dialect::{DialectInfo, PlatformDialect};
use razeri_plugin_api::razeri_plugin_contract::{
render_data_type_ansi, DataType, DdlRenderer, DialectCapabilities,
DialectFamily, DmlRenderer, ExpressionRenderer, FormulaRenderer,
ProcedureRenderer, ReplicationRenderer, SqlRenderer, StorageRenderer,
TaskRenderer,
};
struct MyDialect;
// SqlRenderer: 4 required methods; everything else defaults to ANSI behaviour.
impl SqlRenderer for MyDialect {
fn render_data_type(&self, dt: &DataType) -> String { render_data_type_ansi(dt) }
fn quote_identifier(&self, name: &str) -> String { format!("\"{name}\"") }
fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
match schema {
Some(s) => format!("{s}.{name}"),
None => name.to_string(),
}
}
fn render_current_timestamp(&self) -> String { "CURRENT_TIMESTAMP".into() }
}
// Remaining traits: all methods have defaults — each override is a decision
// your engine states about itself.
impl ExpressionRenderer for MyDialect {}
impl DdlRenderer for MyDialect {}
impl DmlRenderer for MyDialect {}
impl ProcedureRenderer for MyDialect {}
impl FormulaRenderer for MyDialect {}
impl TaskRenderer for MyDialect {}
impl ReplicationRenderer for MyDialect {}
impl StorageRenderer for MyDialect {}
impl DialectCapabilities for MyDialect {}
impl PlatformDialect for MyDialect {
fn info(&self) -> DialectInfo {
DialectInfo {
name: "my-database",
display_name: "My Database",
// The other spellings a design may use for this platform.
aliases: &["mydb"],
dialect_family: DialectFamily::Custom("my-database"),
family: "My Database SQL",
is_cloud_native: false,
is_olap: false,
is_postgres_compatible: false,
// The versions of the engine this dialect answers for, the floor
// first; `&[]` for a managed service that chooses its own.
versions: &[EngineVersion::declared("2024")],
// Which client a bundle's deploy script drives: one razeri writes
// a script for, one described with `DeployScript::Client`, or
// `None` for no script.
deploy_script: None,
}
}
}
fn register(builder: &mut razeri_plugin_api::PluginRegistryBuilder) {
builder.register_dialect(Arc::new(MyDialect));
}
// One line makes the crate a plugin, loaded from a directory or linked in.
razeri_plugin_api::export_plugin!(register);
```
## Running a plugin
A plugin reaches razeri one of two ways, and `export_plugin!` serves both:
- **Loaded from a plugin directory.** Build the crate as a `cdylib` and put the
library in the directory `razeri --plugin-dir` names. It shares Rust types
with the razeri that loads it, so it must be built with the same compiler,
against the same versions of this crate and `razeri-plugin-contract`, from
the same source. `razeri plugin list` prints what that razeri was built
with, and a library built any other way is refused with a message saying
what to rebuild it with.
- **Linked into a razeri you build.** Depend on the crate from a program whose
`main` runs razeri's command line, and the dialect is one of the platforms it
renders. One build means nothing to match.
The book's *Dynamic loading* and *Linking a dialect* pages give both in full.
The book comes with each release, on the
[releases page](https://github.com/gaoash/razeri-releases/releases).
## Proving a plugin
The conformance kit runs from your own test suite and grades the plugin the
way Razeri grades its shipped dialects — defaulted decisions, contradictory
capability answers, missing refusal wording:
```rust
let report = razeri_plugin_api::conformance::check(&MyDialect);
assert!(report.is_clean(), "{report}");
```
Your dialect's names are yours: `name` is the one Razeri reports, `aliases` is
every other spelling a design may use for the same platform, and the resolver
reads both off the dialects that are loaded. A clean report never depends on
anything inside Razeri — only on not claiming a spelling another loaded platform
already answers to.
## Stability
This crate and `razeri-plugin-contract` follow semantic versioning while
the major version is zero. A trait gains a method only with a default body,
so a new method costs an existing plugin no line of code. A change that does
break a plugin's build comes only in a minor release, and the
`CHANGELOG.md` in this package, and the one in `razeri-plugin-contract`'s, say what to change. A method that leaves does so as `#[deprecated]` for one minor
version before it is removed, so a plugin sees the warning before the
removal, not after.
## Features
- **`dynamic`** (default) — Enables runtime plugin loading via shared libraries (`libloading`).
## Documentation
- The book's *Plugin Authoring Guide* and *Plugin Architecture* pages, in the
book that comes with each release on the
[releases page](https://github.com/gaoash/razeri-releases/releases).
- [API reference](https://docs.rs/razeri-plugin-api), on docs.rs.
## License
Dual-licensed under [MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE), at
your option — this crate is the open surface of an otherwise proprietary
workspace, so plugin authors depend on it freely. The rest of Razeri is
covered by the [licence it is released under](https://github.com/gaoash/razeri-releases/blob/main/LICENSE)
(see its §4, "Plugins are open").
## Security
Report a vulnerability as Razeri's [security policy](https://github.com/gaoash/razeri-releases/blob/main/SECURITY.md) says.