mod actor;
mod auth;
mod blob;
mod captcha;
mod classifier;
mod clock;
mod custom_hostnames;
mod database;
mod defer;
mod dispatcher;
mod embedder;
mod http;
mod idgen;
mod inbound_mail;
mod kv;
mod mailer;
mod payments;
mod push;
mod rate_limiter;
mod realtime;
pub(crate) mod signer;
mod speech;
mod text_model;
mod tracker;
mod url_safety;
mod vector_index;
pub use actor::{
ACTOR_CALL_TIMEOUT, ActorContext, ActorError, ActorHandler, ActorHandlers, ActorStore,
ActorWrites, Actors, MAX_ACTOR_KEY_BYTES, MAX_ACTOR_KIND_BYTES, MAX_ACTOR_MESSAGE_BYTES,
MAX_ACTOR_REPLY_BYTES, MAX_ACTOR_VALUE_BYTES, ScopedActors, check_actor_call, run_actor_alarm,
run_actor_message, validate_actor_key, validate_actor_kind,
};
pub use auth::{Auth, AuthError, Caller, Subject, Unconfigured};
pub use blob::{
Blob, BlobError, BlobMeta, BlobObject, BlobPage, BlobStream, DEFAULT_PRESIGN_TTL,
MAX_BLOB_BYTES, MAX_LARGE_BLOB_BYTES, MAX_LIST_LIMIT, MAX_MULTIPART_PARTS, MAX_PRESIGN_TTL,
MIN_MULTIPART_PART_BYTES, PartReceipt, PendingUpload, PresignedPut, ScopedBlob, UploadId,
blob_error_from_stream, check_blob_size, check_part_number, limit_stream,
};
pub use captcha::{Captcha, CaptchaBinding, CaptchaError, Verdict};
pub use classifier::{
Answer, AnswerValue, Calibration, Classifier, ClassifierError, ClassifierProfile,
DEFAULT_MAX_STATE_CHARS, Question, validate_questions,
};
pub use clock::{Clock, SystemClock, timeout};
pub use custom_hostnames::{
CertificateStatus, CustomHostname, CustomHostnameError, CustomHostnames, DnsRecordType,
HostnameClaim, HostnameRefusal, ProviderStatus, Validation, ValidationMethod, check_hostname,
};
pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
pub use defer::{Defer, NoopDefer};
pub use dispatcher::{DispatchError, Dispatcher};
pub use embedder::{EmbedError, Embedder, Embeddings};
pub use http::{
BoundedHttpClient, ByteStream, DEFAULT_RESPONSE_TIMEOUT, DEFAULT_STREAM_IDLE_TIMEOUT,
DEFAULT_STREAM_TOTAL_TIMEOUT, HttpClient, HttpError, HttpPolicy, MAX_CONCURRENT_REQUESTS,
MAX_RESPONSE_BYTES, MAX_RESPONSE_TIMEOUT, MAX_STREAM_BYTES, MAX_STREAM_IDLE_TIMEOUT,
MAX_STREAM_TOTAL_TIMEOUT, StatusOnly, StreamPolicy, declared_content_length,
};
pub use idgen::{IdGen, UlidIdGen};
pub use inbound_mail::{InboundMailError, InboundMailSource, InboundMessage, receive_mail};
pub use kv::{KeyValue, KvError};
pub use mailer::{MailError, MailProvider, Mailer, Message, SendOutcome};
pub use payments::{
Charge, CheckoutRequest, CheckoutSession, ConnectAccountLink, ConnectAccountLinkRequest,
Dispute, DisputeListRequest, DisputePage, DisputePhase, DisputeStatus, LineItem, Money,
Payments, PaymentsError, PortalSession, PortalSessionRequest, Refund, RefundRequest,
Subscription, SubscriptionCheckoutRequest, SubscriptionStatus, TransferCharge, UsageReport,
UsageReported, WebhookEvent,
};
pub use push::{
LocKeys, Notification, Platform, Priority, Push, PushError, PushOutcome, Recipient,
RoutingPush, retry_after, ttl_secs,
};
pub use rate_limiter::{Decision, Quota, RateLimitError, RateLimiter};
pub use realtime::{Member, Realtime, RealtimeError, RoomContext, RoomHandler};
pub use signer::{Kid, MAX_KID_NAME, Payload, SignatureError, Signer};
pub use speech::{
AudioFormat, DEFAULT_MAX_AUDIO_BYTES, DEFAULT_MAX_AUDIO_SECONDS, DEFAULT_MAX_SYNTHESIS_CHARS,
NotConfiguredSpeech, Speech, SpeechCapabilities, SpeechError, SpeechLimit, SpeechLimits,
SpeechUsage, Synthesis, SynthesizeRequest, TranscribeRequest, Transcript, Voice, WordTiming,
check_synthesize, check_transcribe, primary_subtag, wav_duration,
};
pub use text_model::{
Capability, Completion, CompletionBuilder, DEFAULT_MAX_TOKENS, FinishReason, ImageLimit,
ImageMediaType, MAX_IMAGE_ENCODED_BYTES, MAX_PROMPT_IMAGE_ENCODED_BYTES, MAX_PROMPT_IMAGES,
ModelTier, Part, Prompt, Role, RoutingTextModel, TextDelta, TextModel, TextModelError,
ToolCall, ToolChoice, ToolResult, ToolSpec, Turn, completion_deltas, encode_image,
encoded_image_len, stream_owned,
};
pub use tracker::{
Credential, Destination, Filed, InboundStatusError, RoutingTracker, Severity, StatusUpdate,
StatusWebhook, TicketComment, TicketDraft, TicketState, TicketStatus, Tracker, TrackerError,
receive_status,
};
pub use url_safety::{
CachedUrlSafety, DEFAULT_CACHE_CAPACITY, DEFAULT_MAX_BATCH, DEFAULT_MAX_URL_LEN,
DEFAULT_NEGATIVE_TTL, ThreatType, UrlSafety, UrlSafetyError, UrlSafetyLimit, UrlSafetyLimits,
UrlSafetySource, UrlVerdict, check_batch, check_url,
};
pub use vector_index::{
ExactVectorIndex, MAX_NAMESPACE_BYTES, VectorFilter, VectorIndex, VectorIndexError,
VectorMatch, VectorNamespace, VectorRecord,
};
use crate::config::Config;
use crate::module::Module;
use std::sync::Arc;
use tracing::warn;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Port {
Db,
Auth,
Mailer,
Captcha,
RateLimiter,
Signer,
KeyValue,
Blob,
Push,
Payments,
Tracker,
Realtime,
TextModel,
Classifier,
VectorIndex,
Embedder,
CustomHostnames,
UrlSafety,
HttpClient,
Clock,
IdGen,
Defer,
Actor,
}
macro_rules! ports {
($($variant:ident),+ $(,)?) => {
impl Port {
pub const ALL: &'static [Port] = &[$(Port::$variant),+];
}
#[expect(dead_code, reason = "its only job is to be exhaustive")]
fn every_port_is_in_all(port: Port) {
match port {
$(Port::$variant => {}),+
}
}
};
}
ports!(
Db,
Auth,
Mailer,
Captcha,
RateLimiter,
Signer,
KeyValue,
Blob,
Push,
Payments,
Tracker,
Realtime,
TextModel,
Classifier,
VectorIndex,
Embedder,
CustomHostnames,
UrlSafety,
HttpClient,
Clock,
IdGen,
Defer,
Actor,
);
impl Port {
pub fn name(&self) -> &'static str {
match self {
Port::Db => "Database",
Port::Auth => "Auth",
Port::Mailer => "Mailer",
Port::Captcha => "Captcha",
Port::RateLimiter => "RateLimiter",
Port::Signer => "Signer",
Port::KeyValue => "KeyValue",
Port::Blob => "Blob",
Port::Push => "Push",
Port::Payments => "Payments",
Port::Tracker => "Tracker",
Port::Realtime => "Realtime",
Port::TextModel => "TextModel",
Port::Classifier => "Classifier",
Port::VectorIndex => "vector_index",
Port::Embedder => "embedder",
Port::CustomHostnames => "custom_hostnames",
Port::UrlSafety => "url_safety",
Port::HttpClient => "HttpClient",
Port::Clock => "Clock",
Port::IdGen => "IdGen",
Port::Defer => "Defer",
Port::Actor => "actor",
}
}
}
pub struct Ports {
pub config: Arc<dyn Config>,
pub db: Option<Arc<dyn Database>>,
pub auth: Option<Arc<dyn Auth>>,
pub mailer: Option<Arc<dyn Mailer>>,
pub captcha: Option<Arc<dyn Captcha>>,
pub rate_limiter: Option<Arc<dyn RateLimiter>>,
pub signer: Option<Arc<dyn Signer>>,
pub kv: Option<Arc<dyn KeyValue>>,
pub blob: Option<Arc<dyn Blob>>,
pub push: Option<Arc<dyn Push>>,
pub payments: Option<Arc<dyn Payments>>,
pub tracker: Option<Arc<dyn Tracker>>,
pub realtime: Option<Arc<dyn Realtime>>,
pub text_model: Option<Arc<dyn TextModel>>,
pub classifier: Option<Arc<dyn Classifier>>,
pub vector_index: Option<Arc<dyn VectorIndex>>,
pub embedder: Option<Arc<dyn Embedder>>,
pub custom_hostnames: Option<Arc<dyn CustomHostnames>>,
pub url_safety: Option<Arc<dyn UrlSafety>>,
pub http: Option<Arc<dyn HttpClient>>,
pub clock: Option<Arc<dyn Clock>>,
pub id_gen: Option<Arc<dyn IdGen>>,
pub defer: Option<Arc<dyn Defer>>,
pub actors: Option<Arc<dyn Actors>>,
pub dispatcher: Option<Arc<dyn Dispatcher>>,
pub tenants: Option<Arc<dyn crate::tenant::TenantRouting>>,
}
impl Ports {
pub fn empty() -> Self {
Self::with_config(Arc::new(crate::config::EmptyConfig))
}
pub fn with_config(config: Arc<dyn Config>) -> Self {
Self {
config,
db: None,
auth: None,
mailer: None,
captcha: None,
rate_limiter: None,
signer: None,
kv: None,
blob: None,
push: None,
payments: None,
tracker: None,
realtime: None,
text_model: None,
classifier: None,
vector_index: None,
embedder: None,
custom_hostnames: None,
url_safety: None,
http: None,
clock: None,
id_gen: None,
defer: None,
actors: None,
dispatcher: None,
tenants: None,
}
}
#[must_use]
pub fn provides(&self) -> Vec<Port> {
Port::ALL
.iter()
.copied()
.filter(|port| self.has(*port))
.collect()
}
#[must_use]
pub fn has(&self, port: Port) -> bool {
match port {
Port::Db => self.db.is_some(),
Port::Auth => self.auth.is_some(),
Port::Mailer => self.mailer.is_some(),
Port::Captcha => self.captcha.is_some(),
Port::RateLimiter => self.rate_limiter.is_some(),
Port::Signer => self.signer.is_some(),
Port::KeyValue => self.kv.is_some(),
Port::Blob => self.blob.is_some(),
Port::Push => self.push.is_some(),
Port::Payments => self.payments.is_some(),
Port::Tracker => self.tracker.is_some(),
Port::Realtime => self.realtime.is_some(),
Port::TextModel => self.text_model.is_some(),
Port::Classifier => self.classifier.is_some(),
Port::VectorIndex => self.vector_index.is_some(),
Port::Embedder => self.embedder.is_some(),
Port::CustomHostnames => self.custom_hostnames.is_some(),
Port::UrlSafety => self.url_safety.is_some(),
Port::HttpClient => self.http.is_some(),
Port::Clock => self.clock.is_some(),
Port::IdGen => self.id_gen.is_some(),
Port::Defer => self.defer.is_some(),
Port::Actor => self.actors.is_some(),
}
}
#[must_use]
pub fn view_for(&self, module: &dyn Module) -> Self {
let declared = module
.requires()
.iter()
.chain(module.optional())
.copied()
.collect::<Vec<_>>();
let allows = |p: &[Port], port: Port| p.contains(&port);
let mut view = Ports::with_config(self.config.clone());
if allows(&declared, Port::Db) {
view.db.clone_from(&self.db);
}
if allows(&declared, Port::Auth) {
view.auth.clone_from(&self.auth);
}
if allows(&declared, Port::Mailer) {
view.mailer.clone_from(&self.mailer);
}
if allows(&declared, Port::Captcha) {
view.captcha.clone_from(&self.captcha);
}
if allows(&declared, Port::RateLimiter) {
view.rate_limiter.clone_from(&self.rate_limiter);
}
if allows(&declared, Port::Signer) {
view.signer.clone_from(&self.signer);
}
if allows(&declared, Port::KeyValue) {
view.kv.clone_from(&self.kv);
}
if allows(&declared, Port::Blob) {
view.blob = self.blob.as_ref().map(|blob| {
Arc::new(
ScopedBlob::new(Arc::clone(blob), module.name())
.with_max_object_bytes(module.max_blob_object_bytes()),
) as Arc<dyn Blob>
});
}
if allows(&declared, Port::Push) {
view.push.clone_from(&self.push);
}
if allows(&declared, Port::Payments) {
view.payments.clone_from(&self.payments);
}
if allows(&declared, Port::Tracker) {
view.tracker.clone_from(&self.tracker);
}
if allows(&declared, Port::Realtime) {
view.realtime.clone_from(&self.realtime);
}
if allows(&declared, Port::TextModel) {
view.text_model.clone_from(&self.text_model);
}
if allows(&declared, Port::Classifier) {
view.classifier.clone_from(&self.classifier);
}
if allows(&declared, Port::VectorIndex) {
view.vector_index.clone_from(&self.vector_index);
}
if allows(&declared, Port::Embedder) {
view.embedder.clone_from(&self.embedder);
}
if allows(&declared, Port::CustomHostnames) {
view.custom_hostnames.clone_from(&self.custom_hostnames);
}
if allows(&declared, Port::UrlSafety) {
view.url_safety.clone_from(&self.url_safety);
}
if allows(&declared, Port::HttpClient) {
view.http.clone_from(&self.http);
}
if allows(&declared, Port::Clock) {
view.clock.clone_from(&self.clock);
}
if allows(&declared, Port::IdGen) {
view.id_gen.clone_from(&self.id_gen);
}
if allows(&declared, Port::Defer) {
view.defer.clone_from(&self.defer);
}
if allows(&declared, Port::Actor) {
view.actors = self.actors.as_ref().map(|actors| {
Arc::new(ScopedActors::new(
Arc::clone(actors),
module.name(),
module.actor_kinds(),
self.clock.clone(),
)) as Arc<dyn Actors>
});
}
view
}
}
pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
for port in provided {
if !module.requires().contains(port) && !module.optional().contains(port) {
warn!(
module = module.name(),
port = port.name(),
"runtime provides a port the module did not declare; hiding it",
);
}
}
}