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 text_model;
30mod tracker;
31mod vector_index;
32
33pub use actor::{
34 ACTOR_CALL_TIMEOUT, ActorContext, ActorError, ActorHandler, ActorHandlers, ActorStore,
35 ActorWrites, Actors, MAX_ACTOR_KEY_BYTES, MAX_ACTOR_KIND_BYTES, MAX_ACTOR_MESSAGE_BYTES,
36 MAX_ACTOR_REPLY_BYTES, MAX_ACTOR_VALUE_BYTES, ScopedActors, check_actor_call, run_actor_alarm,
37 run_actor_message, validate_actor_key, validate_actor_kind,
38};
39pub use auth::{Auth, AuthError, Caller, Subject, Unconfigured};
40pub use blob::{
41 Blob, BlobError, BlobMeta, BlobObject, BlobPage, BlobStream, DEFAULT_PRESIGN_TTL,
42 MAX_BLOB_BYTES, MAX_LARGE_BLOB_BYTES, MAX_LIST_LIMIT, MAX_MULTIPART_PARTS, MAX_PRESIGN_TTL,
43 MIN_MULTIPART_PART_BYTES, PartReceipt, PendingUpload, PresignedPut, ScopedBlob, UploadId,
44 blob_error_from_stream, check_blob_size, check_part_number, limit_stream,
45};
46pub use captcha::{Captcha, CaptchaBinding, CaptchaError, Verdict};
47pub use classifier::{
48 Answer, AnswerValue, Calibration, Classifier, ClassifierError, ClassifierProfile,
49 DEFAULT_MAX_STATE_CHARS, Question, validate_questions,
50};
51pub use clock::{Clock, SystemClock, timeout};
52pub use custom_hostnames::{
53 CertificateStatus, CustomHostname, CustomHostnameError, CustomHostnames, DnsRecordType,
54 HostnameClaim, HostnameRefusal, ProviderStatus, Validation, ValidationMethod, check_hostname,
55};
56pub use database::{Database, DbError, Row, Rows, Statement, TryFromValue};
57pub use defer::{Defer, NoopDefer};
58pub use dispatcher::{DispatchError, Dispatcher};
59pub use embedder::{EmbedError, Embedder, Embeddings};
60pub use http::{
61 BoundedHttpClient, DEFAULT_RESPONSE_TIMEOUT, HttpClient, HttpError, HttpPolicy,
62 MAX_CONCURRENT_REQUESTS, MAX_RESPONSE_BYTES, MAX_RESPONSE_TIMEOUT, StatusOnly,
63 declared_content_length,
64};
65pub use idgen::{IdGen, UlidIdGen};
66pub use inbound_mail::{InboundMailError, InboundMailSource, InboundMessage, receive_mail};
67pub use kv::{KeyValue, KvError};
68pub use mailer::{MailError, Mailer, Message, SendOutcome};
69pub use payments::{
70 Charge, CheckoutRequest, CheckoutSession, ConnectAccountLink, ConnectAccountLinkRequest,
71 Dispute, DisputeListRequest, DisputePage, DisputePhase, DisputeStatus, LineItem, Money,
72 Payments, PaymentsError, PortalSession, PortalSessionRequest, Refund, RefundRequest,
73 Subscription, SubscriptionCheckoutRequest, SubscriptionStatus, TransferCharge, UsageReport,
74 UsageReported, WebhookEvent,
75};
76pub use push::{
77 LocKeys, Notification, Platform, Priority, Push, PushError, PushOutcome, Recipient,
78 RoutingPush, retry_after, ttl_secs,
79};
80pub use rate_limiter::{Decision, Quota, RateLimitError, RateLimiter};
81pub use realtime::{Member, Realtime, RealtimeError, RoomContext, RoomHandler};
82pub use signer::{Kid, MAX_KID_NAME, Payload, SignatureError, Signer};
83pub use text_model::{
84 Capability, Completion, DEFAULT_MAX_TOKENS, ImageLimit, ImageMediaType,
85 MAX_IMAGE_ENCODED_BYTES, MAX_PROMPT_IMAGE_ENCODED_BYTES, MAX_PROMPT_IMAGES, ModelTier, Part,
86 Prompt, Role, RoutingTextModel, TextModel, TextModelError, ToolCall, ToolChoice, ToolResult,
87 ToolSpec, Turn, encode_image, encoded_image_len,
88};
89pub use tracker::{
90 Credential, Destination, Filed, InboundStatusError, RoutingTracker, Severity, StatusUpdate,
91 StatusWebhook, TicketComment, TicketDraft, TicketState, TicketStatus, Tracker, TrackerError,
92 receive_status,
93};
94pub use vector_index::{
95 ExactVectorIndex, MAX_NAMESPACE_BYTES, VectorFilter, VectorIndex, VectorIndexError,
96 VectorMatch, VectorNamespace, VectorRecord,
97};
98
99use crate::config::Config;
100use crate::module::Module;
101use std::sync::Arc;
102use tracing::warn;
103
104#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
107pub enum Port {
108 Db,
109 Auth,
110 Mailer,
111 Captcha,
112 RateLimiter,
113 Signer,
114 KeyValue,
115 Blob,
116 Push,
117 Payments,
118 Tracker,
119 Realtime,
120 TextModel,
121 Classifier,
122 VectorIndex,
123 Embedder,
124 CustomHostnames,
125 HttpClient,
126 Clock,
127 IdGen,
128 Defer,
129 Actor,
133}
134
135macro_rules! ports {
148 ($($variant:ident),+ $(,)?) => {
149 impl Port {
150 pub const ALL: &'static [Port] = &[$(Port::$variant),+];
152 }
153
154 #[expect(dead_code, reason = "its only job is to be exhaustive")]
157 fn every_port_is_in_all(port: Port) {
158 match port {
159 $(Port::$variant => {}),+
160 }
161 }
162 };
163}
164
165ports!(
166 Db,
167 Auth,
168 Mailer,
169 Captcha,
170 RateLimiter,
171 Signer,
172 KeyValue,
173 Blob,
174 Push,
175 Payments,
176 Tracker,
177 Realtime,
178 TextModel,
179 Classifier,
180 VectorIndex,
181 Embedder,
182 CustomHostnames,
183 HttpClient,
184 Clock,
185 IdGen,
186 Defer,
187 Actor,
188);
189
190impl Port {
191 pub fn name(&self) -> &'static str {
192 match self {
193 Port::Db => "Database",
194 Port::Auth => "Auth",
195 Port::Mailer => "Mailer",
196 Port::Captcha => "Captcha",
197 Port::RateLimiter => "RateLimiter",
198 Port::Signer => "Signer",
199 Port::KeyValue => "KeyValue",
200 Port::Blob => "Blob",
201 Port::Push => "Push",
202 Port::Payments => "Payments",
203 Port::Tracker => "Tracker",
204 Port::Realtime => "Realtime",
205 Port::TextModel => "TextModel",
206 Port::Classifier => "Classifier",
207 Port::VectorIndex => "vector_index",
208 Port::Embedder => "embedder",
209 Port::CustomHostnames => "custom_hostnames",
210 Port::HttpClient => "HttpClient",
211 Port::Clock => "Clock",
212 Port::IdGen => "IdGen",
213 Port::Defer => "Defer",
214 Port::Actor => "actor",
215 }
216 }
217}
218
219pub struct Ports {
225 pub config: Arc<dyn Config>,
226 pub db: Option<Arc<dyn Database>>,
227 pub auth: Option<Arc<dyn Auth>>,
231 pub mailer: Option<Arc<dyn Mailer>>,
232 pub captcha: Option<Arc<dyn Captcha>>,
233 pub rate_limiter: Option<Arc<dyn RateLimiter>>,
234 pub signer: Option<Arc<dyn Signer>>,
235 pub kv: Option<Arc<dyn KeyValue>>,
236 pub blob: Option<Arc<dyn Blob>>,
237 pub push: Option<Arc<dyn Push>>,
238 pub payments: Option<Arc<dyn Payments>>,
239 pub tracker: Option<Arc<dyn Tracker>>,
240 pub realtime: Option<Arc<dyn Realtime>>,
241 pub text_model: Option<Arc<dyn TextModel>>,
244 pub classifier: Option<Arc<dyn Classifier>>,
248 pub vector_index: Option<Arc<dyn VectorIndex>>,
251 pub embedder: Option<Arc<dyn Embedder>>,
254 pub custom_hostnames: Option<Arc<dyn CustomHostnames>>,
258 pub http: Option<Arc<dyn HttpClient>>,
259 pub clock: Option<Arc<dyn Clock>>,
260 pub id_gen: Option<Arc<dyn IdGen>>,
261 pub defer: Option<Arc<dyn Defer>>,
262 pub actors: Option<Arc<dyn Actors>>,
266 pub dispatcher: Option<Arc<dyn Dispatcher>>,
269 pub tenants: Option<Arc<dyn crate::tenant::TenantRouting>>,
280}
281
282impl Ports {
283 pub fn empty() -> Self {
286 Self::with_config(Arc::new(crate::config::EmptyConfig))
287 }
288
289 pub fn with_config(config: Arc<dyn Config>) -> Self {
290 Self {
291 config,
292 db: None,
293 auth: None,
294 mailer: None,
295 captcha: None,
296 rate_limiter: None,
297 signer: None,
298 kv: None,
299 blob: None,
300 push: None,
301 payments: None,
302 tracker: None,
303 realtime: None,
304 text_model: None,
305 classifier: None,
306 vector_index: None,
307 embedder: None,
308 custom_hostnames: None,
309 http: None,
310 clock: None,
311 id_gen: None,
312 defer: None,
313 actors: None,
314 dispatcher: None,
315 tenants: None,
316 }
317 }
318
319 #[must_use]
328 pub fn provides(&self) -> Vec<Port> {
329 Port::ALL
330 .iter()
331 .copied()
332 .filter(|port| self.has(*port))
333 .collect()
334 }
335
336 #[must_use]
342 pub fn has(&self, port: Port) -> bool {
343 match port {
344 Port::Db => self.db.is_some(),
345 Port::Auth => self.auth.is_some(),
346 Port::Mailer => self.mailer.is_some(),
347 Port::Captcha => self.captcha.is_some(),
348 Port::RateLimiter => self.rate_limiter.is_some(),
349 Port::Signer => self.signer.is_some(),
350 Port::KeyValue => self.kv.is_some(),
351 Port::Blob => self.blob.is_some(),
352 Port::Push => self.push.is_some(),
353 Port::Payments => self.payments.is_some(),
354 Port::Tracker => self.tracker.is_some(),
355 Port::Realtime => self.realtime.is_some(),
356 Port::TextModel => self.text_model.is_some(),
357 Port::Classifier => self.classifier.is_some(),
358 Port::VectorIndex => self.vector_index.is_some(),
359 Port::Embedder => self.embedder.is_some(),
360 Port::CustomHostnames => self.custom_hostnames.is_some(),
361 Port::HttpClient => self.http.is_some(),
362 Port::Clock => self.clock.is_some(),
363 Port::IdGen => self.id_gen.is_some(),
364 Port::Defer => self.defer.is_some(),
365 Port::Actor => self.actors.is_some(),
366 }
367 }
368
369 #[must_use]
374 pub fn view_for(&self, module: &dyn Module) -> Self {
375 let declared = module
376 .requires()
377 .iter()
378 .chain(module.optional())
379 .copied()
380 .collect::<Vec<_>>();
381 let allows = |p: &[Port], port: Port| p.contains(&port);
382 let mut view = Ports::with_config(self.config.clone());
383 if allows(&declared, Port::Db) {
384 view.db.clone_from(&self.db);
385 }
386 if allows(&declared, Port::Auth) {
387 view.auth.clone_from(&self.auth);
388 }
389 if allows(&declared, Port::Mailer) {
390 view.mailer.clone_from(&self.mailer);
391 }
392 if allows(&declared, Port::Captcha) {
393 view.captcha.clone_from(&self.captcha);
394 }
395 if allows(&declared, Port::RateLimiter) {
396 view.rate_limiter.clone_from(&self.rate_limiter);
397 }
398 if allows(&declared, Port::Signer) {
399 view.signer.clone_from(&self.signer);
400 }
401 if allows(&declared, Port::KeyValue) {
402 view.kv.clone_from(&self.kv);
403 }
404 if allows(&declared, Port::Blob) {
405 view.blob = self.blob.as_ref().map(|blob| {
410 Arc::new(
411 ScopedBlob::new(Arc::clone(blob), module.name())
412 .with_max_object_bytes(module.max_blob_object_bytes()),
413 ) as Arc<dyn Blob>
414 });
415 }
416 if allows(&declared, Port::Push) {
417 view.push.clone_from(&self.push);
418 }
419 if allows(&declared, Port::Payments) {
420 view.payments.clone_from(&self.payments);
421 }
422 if allows(&declared, Port::Tracker) {
423 view.tracker.clone_from(&self.tracker);
424 }
425 if allows(&declared, Port::Realtime) {
426 view.realtime.clone_from(&self.realtime);
427 }
428 if allows(&declared, Port::TextModel) {
429 view.text_model.clone_from(&self.text_model);
430 }
431 if allows(&declared, Port::Classifier) {
432 view.classifier.clone_from(&self.classifier);
433 }
434 if allows(&declared, Port::VectorIndex) {
435 view.vector_index.clone_from(&self.vector_index);
436 }
437 if allows(&declared, Port::Embedder) {
438 view.embedder.clone_from(&self.embedder);
439 }
440 if allows(&declared, Port::CustomHostnames) {
441 view.custom_hostnames.clone_from(&self.custom_hostnames);
442 }
443 if allows(&declared, Port::HttpClient) {
444 view.http.clone_from(&self.http);
445 }
446 if allows(&declared, Port::Clock) {
447 view.clock.clone_from(&self.clock);
448 }
449 if allows(&declared, Port::IdGen) {
450 view.id_gen.clone_from(&self.id_gen);
451 }
452 if allows(&declared, Port::Defer) {
453 view.defer.clone_from(&self.defer);
454 }
455 if allows(&declared, Port::Actor) {
456 view.actors = self.actors.as_ref().map(|actors| {
459 Arc::new(ScopedActors::new(
460 Arc::clone(actors),
461 module.name(),
462 module.actor_kinds(),
463 self.clock.clone(),
464 )) as Arc<dyn Actors>
465 });
466 }
467 view
468 }
469}
470
471pub(crate) fn warn_undeclared_ports(module: &dyn Module, provided: &[Port]) {
474 for port in provided {
475 if !module.requires().contains(port) && !module.optional().contains(port) {
476 warn!(
477 module = module.name(),
478 port = port.name(),
479 "runtime provides a port the module did not declare; hiding it",
480 );
481 }
482 }
483}