1mod actor;
9mod auth;
10mod blob;
11mod captcha;
12mod classifier;
13mod clock;
14mod custom_hostnames;
15mod database;
16mod defer;
17mod dispatcher;
18mod embedder;
19mod http;
20mod idgen;
21mod inbound_mail;
22mod kv;
23mod mailer;
24mod payments;
25mod push;
26mod rate_limiter;
27mod realtime;
28pub(crate) mod signer;
29mod speech;
30mod text_model;
31mod tracker;
32mod vector_index;
33
34pub use actor::{
35 ACTOR_CALL_TIMEOUT, ActorContext, ActorError, ActorHandler, ActorHandlers, ActorStore,
36 ActorWrites, Actors, MAX_ACTOR_KEY_BYTES, MAX_ACTOR_KIND_BYTES, MAX_ACTOR_MESSAGE_BYTES,
37 MAX_ACTOR_REPLY_BYTES, MAX_ACTOR_VALUE_BYTES, ScopedActors, check_actor_call, run_actor_alarm,
38 run_actor_message, validate_actor_key, validate_actor_kind,
39};
40pub use auth::{Auth, AuthError, Caller, Subject, Unconfigured};
41pub use blob::{
42 Blob, BlobError, BlobMeta, BlobObject, BlobPage, BlobStream, DEFAULT_PRESIGN_TTL,
43 MAX_BLOB_BYTES, MAX_LARGE_BLOB_BYTES, MAX_LIST_LIMIT, MAX_MULTIPART_PARTS, MAX_PRESIGN_TTL,
44 MIN_MULTIPART_PART_BYTES, PartReceipt, PendingUpload, PresignedPut, ScopedBlob, UploadId,
45 blob_error_from_stream, check_blob_size, check_part_number, limit_stream,
46};
47pub use captcha::{Captcha, CaptchaBinding, CaptchaError, Verdict};
48pub use classifier::{
49 Answer, AnswerValue, Calibration, Classifier, ClassifierError, ClassifierProfile,
50 DEFAULT_MAX_STATE_CHARS, Question, validate_questions,
51};
52pub use clock::{Clock, SystemClock, timeout};
53pub use custom_hostnames::{
54 CertificateStatus, CustomHostname, CustomHostnameError, CustomHostnames, DnsRecordType,
55 HostnameClaim, HostnameRefusal, ProviderStatus, Validation, ValidationMethod, check_hostname,
56};
57pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
58pub use defer::{Defer, NoopDefer};
59pub use dispatcher::{DispatchError, Dispatcher};
60pub use embedder::{EmbedError, Embedder, Embeddings};
61pub use http::{
62 BoundedHttpClient, ByteStream, DEFAULT_RESPONSE_TIMEOUT, DEFAULT_STREAM_IDLE_TIMEOUT,
63 DEFAULT_STREAM_TOTAL_TIMEOUT, HttpClient, HttpError, HttpPolicy, MAX_CONCURRENT_REQUESTS,
64 MAX_RESPONSE_BYTES, MAX_RESPONSE_TIMEOUT, MAX_STREAM_BYTES, MAX_STREAM_IDLE_TIMEOUT,
65 MAX_STREAM_TOTAL_TIMEOUT, StatusOnly, StreamPolicy, declared_content_length,
66};
67pub use idgen::{IdGen, UlidIdGen};
68pub use inbound_mail::{InboundMailError, InboundMailSource, InboundMessage, receive_mail};
69pub use kv::{KeyValue, KvError};
70pub use mailer::{MailError, MailProvider, Mailer, Message, SendOutcome};
71pub use payments::{
72 Charge, CheckoutRequest, CheckoutSession, ConnectAccountLink, ConnectAccountLinkRequest,
73 Dispute, DisputeListRequest, DisputePage, DisputePhase, DisputeStatus, LineItem, Money,
74 Payments, PaymentsError, PortalSession, PortalSessionRequest, Refund, RefundRequest,
75 Subscription, SubscriptionCheckoutRequest, SubscriptionStatus, TransferCharge, UsageReport,
76 UsageReported, WebhookEvent,
77};
78pub use push::{
79 LocKeys, Notification, Platform, Priority, Push, PushError, PushOutcome, Recipient,
80 RoutingPush, retry_after, ttl_secs,
81};
82pub use rate_limiter::{Decision, Quota, RateLimitError, RateLimiter};
83pub use realtime::{Member, Realtime, RealtimeError, RoomContext, RoomHandler};
84pub use signer::{Kid, MAX_KID_NAME, Payload, SignatureError, Signer};
85pub use speech::{
86 AudioFormat, DEFAULT_MAX_AUDIO_BYTES, DEFAULT_MAX_AUDIO_SECONDS, DEFAULT_MAX_SYNTHESIS_CHARS,
87 NotConfiguredSpeech, Speech, SpeechCapabilities, SpeechError, SpeechLimit, SpeechLimits,
88 SpeechUsage, Synthesis, SynthesizeRequest, TranscribeRequest, Transcript, Voice, WordTiming,
89 check_synthesize, check_transcribe, primary_subtag, wav_duration,
90};
91pub use text_model::{
92 Capability, Completion, CompletionBuilder, DEFAULT_MAX_TOKENS, FinishReason, ImageLimit,
93 ImageMediaType, MAX_IMAGE_ENCODED_BYTES, MAX_PROMPT_IMAGE_ENCODED_BYTES, MAX_PROMPT_IMAGES,
94 ModelTier, Part, Prompt, Role, RoutingTextModel, TextDelta, TextModel, TextModelError,
95 ToolCall, ToolChoice, ToolResult, ToolSpec, Turn, completion_deltas, encode_image,
96 encoded_image_len, stream_owned,
97};
98pub use tracker::{
99 Credential, Destination, Filed, InboundStatusError, RoutingTracker, Severity, StatusUpdate,
100 StatusWebhook, TicketComment, TicketDraft, TicketState, TicketStatus, Tracker, TrackerError,
101 receive_status,
102};
103pub use vector_index::{
104 ExactVectorIndex, MAX_NAMESPACE_BYTES, VectorFilter, VectorIndex, VectorIndexError,
105 VectorMatch, VectorNamespace, VectorRecord,
106};
107
108use crate::config::Config;
109use crate::module::Module;
110use std::sync::Arc;
111use tracing::warn;
112
113#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
116pub enum Port {
117 Db,
118 Auth,
119 Mailer,
120 Captcha,
121 RateLimiter,
122 Signer,
123 KeyValue,
124 Blob,
125 Push,
126 Payments,
127 Tracker,
128 Realtime,
129 TextModel,
130 Classifier,
131 VectorIndex,
132 Embedder,
133 CustomHostnames,
134 HttpClient,
135 Clock,
136 IdGen,
137 Defer,
138 Actor,
142}
143
144macro_rules! ports {
157 ($($variant:ident),+ $(,)?) => {
158 impl Port {
159 pub const ALL: &'static [Port] = &[$(Port::$variant),+];
161 }
162
163 #[expect(dead_code, reason = "its only job is to be exhaustive")]
166 fn every_port_is_in_all(port: Port) {
167 match port {
168 $(Port::$variant => {}),+
169 }
170 }
171 };
172}
173
174ports!(
175 Db,
176 Auth,
177 Mailer,
178 Captcha,
179 RateLimiter,
180 Signer,
181 KeyValue,
182 Blob,
183 Push,
184 Payments,
185 Tracker,
186 Realtime,
187 TextModel,
188 Classifier,
189 VectorIndex,
190 Embedder,
191 CustomHostnames,
192 HttpClient,
193 Clock,
194 IdGen,
195 Defer,
196 Actor,
197);
198
199impl Port {
200 pub fn name(&self) -> &'static str {
201 match self {
202 Port::Db => "Database",
203 Port::Auth => "Auth",
204 Port::Mailer => "Mailer",
205 Port::Captcha => "Captcha",
206 Port::RateLimiter => "RateLimiter",
207 Port::Signer => "Signer",
208 Port::KeyValue => "KeyValue",
209 Port::Blob => "Blob",
210 Port::Push => "Push",
211 Port::Payments => "Payments",
212 Port::Tracker => "Tracker",
213 Port::Realtime => "Realtime",
214 Port::TextModel => "TextModel",
215 Port::Classifier => "Classifier",
216 Port::VectorIndex => "vector_index",
217 Port::Embedder => "embedder",
218 Port::CustomHostnames => "custom_hostnames",
219 Port::HttpClient => "HttpClient",
220 Port::Clock => "Clock",
221 Port::IdGen => "IdGen",
222 Port::Defer => "Defer",
223 Port::Actor => "actor",
224 }
225 }
226}
227
228pub struct Ports {
234 pub config: Arc<dyn Config>,
235 pub db: Option<Arc<dyn Database>>,
236 pub auth: Option<Arc<dyn Auth>>,
240 pub mailer: Option<Arc<dyn Mailer>>,
241 pub captcha: Option<Arc<dyn Captcha>>,
242 pub rate_limiter: Option<Arc<dyn RateLimiter>>,
243 pub signer: Option<Arc<dyn Signer>>,
244 pub kv: Option<Arc<dyn KeyValue>>,
245 pub blob: Option<Arc<dyn Blob>>,
246 pub push: Option<Arc<dyn Push>>,
247 pub payments: Option<Arc<dyn Payments>>,
248 pub tracker: Option<Arc<dyn Tracker>>,
249 pub realtime: Option<Arc<dyn Realtime>>,
250 pub text_model: Option<Arc<dyn TextModel>>,
253 pub classifier: Option<Arc<dyn Classifier>>,
257 pub vector_index: Option<Arc<dyn VectorIndex>>,
260 pub embedder: Option<Arc<dyn Embedder>>,
263 pub custom_hostnames: Option<Arc<dyn CustomHostnames>>,
267 pub http: Option<Arc<dyn HttpClient>>,
268 pub clock: Option<Arc<dyn Clock>>,
269 pub id_gen: Option<Arc<dyn IdGen>>,
270 pub defer: Option<Arc<dyn Defer>>,
271 pub actors: Option<Arc<dyn Actors>>,
275 pub dispatcher: Option<Arc<dyn Dispatcher>>,
278 pub tenants: Option<Arc<dyn crate::tenant::TenantRouting>>,
289}
290
291impl Ports {
292 pub fn empty() -> Self {
295 Self::with_config(Arc::new(crate::config::EmptyConfig))
296 }
297
298 pub fn with_config(config: Arc<dyn Config>) -> Self {
299 Self {
300 config,
301 db: None,
302 auth: None,
303 mailer: None,
304 captcha: None,
305 rate_limiter: None,
306 signer: None,
307 kv: None,
308 blob: None,
309 push: None,
310 payments: None,
311 tracker: None,
312 realtime: None,
313 text_model: None,
314 classifier: None,
315 vector_index: None,
316 embedder: None,
317 custom_hostnames: None,
318 http: None,
319 clock: None,
320 id_gen: None,
321 defer: None,
322 actors: None,
323 dispatcher: None,
324 tenants: None,
325 }
326 }
327
328 #[must_use]
337 pub fn provides(&self) -> Vec<Port> {
338 Port::ALL
339 .iter()
340 .copied()
341 .filter(|port| self.has(*port))
342 .collect()
343 }
344
345 #[must_use]
351 pub fn has(&self, port: Port) -> bool {
352 match port {
353 Port::Db => self.db.is_some(),
354 Port::Auth => self.auth.is_some(),
355 Port::Mailer => self.mailer.is_some(),
356 Port::Captcha => self.captcha.is_some(),
357 Port::RateLimiter => self.rate_limiter.is_some(),
358 Port::Signer => self.signer.is_some(),
359 Port::KeyValue => self.kv.is_some(),
360 Port::Blob => self.blob.is_some(),
361 Port::Push => self.push.is_some(),
362 Port::Payments => self.payments.is_some(),
363 Port::Tracker => self.tracker.is_some(),
364 Port::Realtime => self.realtime.is_some(),
365 Port::TextModel => self.text_model.is_some(),
366 Port::Classifier => self.classifier.is_some(),
367 Port::VectorIndex => self.vector_index.is_some(),
368 Port::Embedder => self.embedder.is_some(),
369 Port::CustomHostnames => self.custom_hostnames.is_some(),
370 Port::HttpClient => self.http.is_some(),
371 Port::Clock => self.clock.is_some(),
372 Port::IdGen => self.id_gen.is_some(),
373 Port::Defer => self.defer.is_some(),
374 Port::Actor => self.actors.is_some(),
375 }
376 }
377
378 #[must_use]
383 pub fn view_for(&self, module: &dyn Module) -> Self {
384 let declared = module
385 .requires()
386 .iter()
387 .chain(module.optional())
388 .copied()
389 .collect::<Vec<_>>();
390 let allows = |p: &[Port], port: Port| p.contains(&port);
391 let mut view = Ports::with_config(self.config.clone());
392 if allows(&declared, Port::Db) {
393 view.db.clone_from(&self.db);
394 }
395 if allows(&declared, Port::Auth) {
396 view.auth.clone_from(&self.auth);
397 }
398 if allows(&declared, Port::Mailer) {
399 view.mailer.clone_from(&self.mailer);
400 }
401 if allows(&declared, Port::Captcha) {
402 view.captcha.clone_from(&self.captcha);
403 }
404 if allows(&declared, Port::RateLimiter) {
405 view.rate_limiter.clone_from(&self.rate_limiter);
406 }
407 if allows(&declared, Port::Signer) {
408 view.signer.clone_from(&self.signer);
409 }
410 if allows(&declared, Port::KeyValue) {
411 view.kv.clone_from(&self.kv);
412 }
413 if allows(&declared, Port::Blob) {
414 view.blob = self.blob.as_ref().map(|blob| {
419 Arc::new(
420 ScopedBlob::new(Arc::clone(blob), module.name())
421 .with_max_object_bytes(module.max_blob_object_bytes()),
422 ) as Arc<dyn Blob>
423 });
424 }
425 if allows(&declared, Port::Push) {
426 view.push.clone_from(&self.push);
427 }
428 if allows(&declared, Port::Payments) {
429 view.payments.clone_from(&self.payments);
430 }
431 if allows(&declared, Port::Tracker) {
432 view.tracker.clone_from(&self.tracker);
433 }
434 if allows(&declared, Port::Realtime) {
435 view.realtime.clone_from(&self.realtime);
436 }
437 if allows(&declared, Port::TextModel) {
438 view.text_model.clone_from(&self.text_model);
439 }
440 if allows(&declared, Port::Classifier) {
441 view.classifier.clone_from(&self.classifier);
442 }
443 if allows(&declared, Port::VectorIndex) {
444 view.vector_index.clone_from(&self.vector_index);
445 }
446 if allows(&declared, Port::Embedder) {
447 view.embedder.clone_from(&self.embedder);
448 }
449 if allows(&declared, Port::CustomHostnames) {
450 view.custom_hostnames.clone_from(&self.custom_hostnames);
451 }
452 if allows(&declared, Port::HttpClient) {
453 view.http.clone_from(&self.http);
454 }
455 if allows(&declared, Port::Clock) {
456 view.clock.clone_from(&self.clock);
457 }
458 if allows(&declared, Port::IdGen) {
459 view.id_gen.clone_from(&self.id_gen);
460 }
461 if allows(&declared, Port::Defer) {
462 view.defer.clone_from(&self.defer);
463 }
464 if allows(&declared, Port::Actor) {
465 view.actors = self.actors.as_ref().map(|actors| {
468 Arc::new(ScopedActors::new(
469 Arc::clone(actors),
470 module.name(),
471 module.actor_kinds(),
472 self.clock.clone(),
473 )) as Arc<dyn Actors>
474 });
475 }
476 view
477 }
478}
479
480pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
483 for port in provided {
484 if !module.requires().contains(port) && !module.optional().contains(port) {
485 warn!(
486 module = module.name(),
487 port = port.name(),
488 "runtime provides a port the module did not declare; hiding it",
489 );
490 }
491 }
492}