feat(v1.1.9): InvokeService trait + InvokeTarget + ScriptRepository::get_by_name
shared/invoke.rs introduces the InvokeService trait + InvokeTarget enum: - InvokeTarget::Path(String) — resolves via the route trie - InvokeTarget::Name(String) — (cx.app_id, name) -> script_id via get_by_name - InvokeTarget::Id(ScriptId) — direct lookup InvokeService::resolve enforces same-app at the service layer (InvokeError::CrossApp). InvokeService::enqueue_async writes a v1.1.9 OutboxSourceKind::Invoke row for fire-and-forget composition. Errors: NotFound, CrossApp, DepthExceeded(u32), Forbidden, Rejected, Unavailable. NoopInvokeService surfaces all calls as Unavailable for harnesses that don't wire the service. ScriptRepository gains get_by_name(app_id, name) backed by the existing (app_id, name) uniqueness constraint. Postgres impl + in-memory mock + the picloud crate's PostgresScriptRepoHandle delegate added. Services::new gains invoke: Arc<dyn InvokeService> positionally after queue. with_noop_services wires NoopInvokeService. 12 test sites threaded through. picloud/lib.rs binds NoopInvokeService at this commit boundary — the real InvokeResolver lands in commit 8. Deviation from plan (flagged for HANDBACK §7): the plan called for moving ExecutorClient + ScriptIdentity from orchestrator-core to shared so the invoke bridge could call a re-entrant variant. Re-evaluated: the bridge lives in executor-core which can hold Arc<Engine> directly, making the trait move unnecessary. Engine::execute is sync, returns ExecResponse, and shares the engine instance + per-call SdkCallCx exactly as required. AST cache reuse across invokes is a v1.2 optimization (invokes recompile each call — ms-scale cost, acceptable). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -105,6 +105,7 @@ async fn original_backend_error_is_logged_at_error_level() {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
let engine = Engine::new(Limits::default(), services);
|
let engine = Engine::new(Limits::default(), services);
|
||||||
|
|
||||||
|
|||||||
@@ -103,6 +103,7 @@ fn services_with(modules: Arc<dyn ModuleSource>) -> Services {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -234,6 +234,7 @@ fn make_engine() -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ fn engine_with(rec: Arc<RecordingEmail>) -> Arc<Engine> {
|
|||||||
rec,
|
rec,
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -171,6 +171,7 @@ fn make_engine() -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ fn engine_with(http: Arc<dyn HttpService>) -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,7 @@ fn make_engine() -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,6 +51,7 @@ fn make_engine(svc: Arc<RecordingPubsub>) -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ fn make_engine(svc: Arc<RecordingQueue>) -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
svc,
|
svc,
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ fn make_engine() -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -98,6 +98,7 @@ fn make_engine() -> Arc<Engine> {
|
|||||||
Arc::new(picloud_shared::NoopEmailService),
|
Arc::new(picloud_shared::NoopEmailService),
|
||||||
Arc::new(picloud_shared::NoopUsersService),
|
Arc::new(picloud_shared::NoopUsersService),
|
||||||
Arc::new(picloud_shared::NoopQueueService),
|
Arc::new(picloud_shared::NoopQueueService),
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService),
|
||||||
);
|
);
|
||||||
Arc::new(Engine::new(Limits::default(), services))
|
Arc::new(Engine::new(Limits::default(), services))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,16 @@ pub enum ScriptRepositoryError {
|
|||||||
#[async_trait]
|
#[async_trait]
|
||||||
pub trait ScriptRepository: Send + Sync {
|
pub trait ScriptRepository: Send + Sync {
|
||||||
async fn get(&self, id: ScriptId) -> Result<Option<Script>, ScriptRepositoryError>;
|
async fn get(&self, id: ScriptId) -> Result<Option<Script>, ScriptRepositoryError>;
|
||||||
|
/// v1.1.9. Resolve `(app_id, name) → Script`. Backs `invoke("name", …)`
|
||||||
|
/// — the SDK lets a caller name a target script by its `scripts.name`
|
||||||
|
/// instead of plumbing UUIDs. Returns `None` if no script in the app
|
||||||
|
/// has that name. Names are unique per `(app_id, name)` already
|
||||||
|
/// (existing migration constraint).
|
||||||
|
async fn get_by_name(
|
||||||
|
&self,
|
||||||
|
app_id: AppId,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<Script>, ScriptRepositoryError>;
|
||||||
/// Every script across all apps. Mostly for tests and admin
|
/// Every script across all apps. Mostly for tests and admin
|
||||||
/// "global" views; the dashboard reaches scripts via `list_for_app`.
|
/// "global" views; the dashboard reaches scripts via `list_for_app`.
|
||||||
async fn list(&self) -> Result<Vec<Script>, ScriptRepositoryError>;
|
async fn list(&self) -> Result<Vec<Script>, ScriptRepositoryError>;
|
||||||
@@ -145,6 +155,21 @@ impl ScriptRepository for PostgresScriptRepository {
|
|||||||
Ok(row.map(Into::into))
|
Ok(row.map(Into::into))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_by_name(
|
||||||
|
&self,
|
||||||
|
app_id: AppId,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<Script>, ScriptRepositoryError> {
|
||||||
|
let row = sqlx::query_as::<_, ScriptRow>(&format!(
|
||||||
|
"SELECT {SCRIPT_SELECT_COLS} FROM scripts WHERE app_id = $1 AND name = $2"
|
||||||
|
))
|
||||||
|
.bind(app_id.into_inner())
|
||||||
|
.bind(name)
|
||||||
|
.fetch_optional(&self.pool)
|
||||||
|
.await?;
|
||||||
|
Ok(row.map(Into::into))
|
||||||
|
}
|
||||||
|
|
||||||
async fn list(&self) -> Result<Vec<Script>, ScriptRepositoryError> {
|
async fn list(&self) -> Result<Vec<Script>, ScriptRepositoryError> {
|
||||||
let rows = sqlx::query_as::<_, ScriptRow>(&format!(
|
let rows = sqlx::query_as::<_, ScriptRow>(&format!(
|
||||||
"SELECT {SCRIPT_SELECT_COLS} FROM scripts ORDER BY name"
|
"SELECT {SCRIPT_SELECT_COLS} FROM scripts ORDER BY name"
|
||||||
|
|||||||
@@ -1153,6 +1153,19 @@ mod tests {
|
|||||||
) -> Result<Option<picloud_shared::Script>, crate::repo::ScriptRepositoryError> {
|
) -> Result<Option<picloud_shared::Script>, crate::repo::ScriptRepositoryError> {
|
||||||
Ok(self.existing.lock().await.get(&id).cloned())
|
Ok(self.existing.lock().await.get(&id).cloned())
|
||||||
}
|
}
|
||||||
|
async fn get_by_name(
|
||||||
|
&self,
|
||||||
|
app_id: AppId,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<picloud_shared::Script>, crate::repo::ScriptRepositoryError> {
|
||||||
|
Ok(self
|
||||||
|
.existing
|
||||||
|
.lock()
|
||||||
|
.await
|
||||||
|
.values()
|
||||||
|
.find(|s| s.app_id == app_id && s.name == name)
|
||||||
|
.cloned())
|
||||||
|
}
|
||||||
async fn list(
|
async fn list(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Vec<picloud_shared::Script>, crate::repo::ScriptRepositoryError> {
|
) -> Result<Vec<picloud_shared::Script>, crate::repo::ScriptRepositoryError> {
|
||||||
|
|||||||
@@ -274,6 +274,11 @@ pub async fn build_app(
|
|||||||
authz.clone(),
|
authz.clone(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
// v1.1.9 function composition. Real InvokeService wired in commit 8;
|
||||||
|
// the noop here keeps the binary compiling — without it, scripts
|
||||||
|
// calling `invoke()` will throw a clear error.
|
||||||
|
let invoke: Arc<dyn picloud_shared::InvokeService> =
|
||||||
|
Arc::new(picloud_shared::NoopInvokeService);
|
||||||
let services = Services::new(
|
let services = Services::new(
|
||||||
kv,
|
kv,
|
||||||
docs,
|
docs,
|
||||||
@@ -287,6 +292,7 @@ pub async fn build_app(
|
|||||||
email,
|
email,
|
||||||
users.clone(),
|
users.clone(),
|
||||||
queue,
|
queue,
|
||||||
|
invoke,
|
||||||
);
|
);
|
||||||
let engine = Arc::new(Engine::new(Limits::default(), services));
|
let engine = Arc::new(Engine::new(Limits::default(), services));
|
||||||
|
|
||||||
@@ -595,6 +601,13 @@ impl picloud_manager_core::ScriptRepository for PostgresScriptRepoHandle {
|
|||||||
) -> Result<Option<picloud_shared::Script>, picloud_manager_core::ScriptRepositoryError> {
|
) -> Result<Option<picloud_shared::Script>, picloud_manager_core::ScriptRepositoryError> {
|
||||||
self.0.get(id).await
|
self.0.get(id).await
|
||||||
}
|
}
|
||||||
|
async fn get_by_name(
|
||||||
|
&self,
|
||||||
|
app_id: picloud_shared::AppId,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Option<picloud_shared::Script>, picloud_manager_core::ScriptRepositoryError> {
|
||||||
|
self.0.get_by_name(app_id, name).await
|
||||||
|
}
|
||||||
async fn list(
|
async fn list(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<Vec<picloud_shared::Script>, picloud_manager_core::ScriptRepositoryError> {
|
) -> Result<Vec<picloud_shared::Script>, picloud_manager_core::ScriptRepositoryError> {
|
||||||
|
|||||||
136
crates/shared/src/invoke.rs
Normal file
136
crates/shared/src/invoke.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
//! `InvokeService` — the v1.1.9 function-composition contract.
|
||||||
|
//!
|
||||||
|
//! Backs the Rhai `invoke(target, args)` and `invoke_async(target, args)`
|
||||||
|
//! SDK calls. Resolves a target (by route path, by script name, or by
|
||||||
|
//! script id) within the **caller's app** to a `ResolvedScript`. Same-
|
||||||
|
//! app only; cross-app calls are rejected at the service entry point
|
||||||
|
//! (v1.1.x maintains strict isolation; cross-app sharing arrives in
|
||||||
|
//! v1.3+).
|
||||||
|
//!
|
||||||
|
//! The re-entrant Rhai pattern lives in `executor-core`: the bridge
|
||||||
|
//! captures `Arc<Engine>`, resolves the target via this trait, builds a
|
||||||
|
//! fresh `ExecRequest` with `trigger_depth + 1`, and calls
|
||||||
|
//! `Engine::execute(&source, req)` synchronously inside the caller's
|
||||||
|
//! `spawn_blocking` thread. Engine + AST cache are shared; only the
|
||||||
|
//! `SdkCallCx` changes per call.
|
||||||
|
|
||||||
|
use async_trait::async_trait;
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{AppId, ExecutionId, ScriptId, SdkCallCx};
|
||||||
|
|
||||||
|
/// Identifies the script to invoke. Accepted by `invoke()` and
|
||||||
|
/// `invoke_async()` script-side.
|
||||||
|
///
|
||||||
|
/// - `Path("/users/:id")` → resolve via the orchestrator's route trie.
|
||||||
|
/// - `Name("payments_worker")` → `(cx.app_id, name) → script_id`.
|
||||||
|
/// - `Id(...)` → direct lookup, same-app verification afterward.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum InvokeTarget {
|
||||||
|
Path(String),
|
||||||
|
Name(String),
|
||||||
|
Id(ScriptId),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InvokeTarget {
|
||||||
|
/// Best-effort summary for error messages.
|
||||||
|
#[must_use]
|
||||||
|
pub fn describe(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Path(p) => format!("path {p:?}"),
|
||||||
|
Self::Name(n) => format!("name {n:?}"),
|
||||||
|
Self::Id(i) => format!("id {i}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The script the invoke service resolved. `app_id` MUST match the
|
||||||
|
/// caller's `cx.app_id` — the service enforces this and returns
|
||||||
|
/// `InvokeError::CrossApp` otherwise.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ResolvedScript {
|
||||||
|
pub script_id: ScriptId,
|
||||||
|
pub app_id: AppId,
|
||||||
|
pub source: String,
|
||||||
|
pub updated_at: DateTime<Utc>,
|
||||||
|
/// Script.name — surfaced to the callee as `ctx.script_name`.
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait InvokeService: Send + Sync {
|
||||||
|
/// Resolve a target to its script + verify same-app. Used by both
|
||||||
|
/// `invoke()` (sync) and `invoke_async()` (fire-and-forget).
|
||||||
|
async fn resolve(
|
||||||
|
&self,
|
||||||
|
cx: &SdkCallCx,
|
||||||
|
target: InvokeTarget,
|
||||||
|
) -> Result<ResolvedScript, InvokeError>;
|
||||||
|
|
||||||
|
/// Fire-and-forget: write a v1.1.9 `OutboxSourceKind::Invoke` row
|
||||||
|
/// the dispatcher consumes; return the new `ExecutionId`. No retry
|
||||||
|
/// (one shot); the script wraps in `retry::with` if it wants more.
|
||||||
|
async fn enqueue_async(
|
||||||
|
&self,
|
||||||
|
cx: &SdkCallCx,
|
||||||
|
target: InvokeTarget,
|
||||||
|
args: serde_json::Value,
|
||||||
|
) -> Result<ExecutionId, InvokeError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
pub enum InvokeError {
|
||||||
|
/// Target script not found in `cx.app_id`.
|
||||||
|
#[error("invoke: target not found ({0})")]
|
||||||
|
NotFound(String),
|
||||||
|
|
||||||
|
/// Target exists but belongs to a different app. v1.1.x rejects
|
||||||
|
/// cross-app invokes outright.
|
||||||
|
#[error("invoke: target script belongs to a different app")]
|
||||||
|
CrossApp,
|
||||||
|
|
||||||
|
/// Depth limit exceeded — `cx.trigger_depth >= limits.trigger_depth_max`.
|
||||||
|
/// Caller's `invoke()` would push the callee past the bound.
|
||||||
|
#[error("invoke: depth limit exceeded (max {0})")]
|
||||||
|
DepthExceeded(u32),
|
||||||
|
|
||||||
|
/// Caller principal lacks the required capability. Anonymous public-
|
||||||
|
/// HTTP scripts skip this check; authenticated callers gate.
|
||||||
|
#[error("forbidden")]
|
||||||
|
Forbidden,
|
||||||
|
|
||||||
|
/// Payload could not be serialized (e.g. a Rhai FnPtr passed across
|
||||||
|
/// the invoke boundary — closures are explicitly rejected).
|
||||||
|
#[error("invoke rejected: {0}")]
|
||||||
|
Rejected(String),
|
||||||
|
|
||||||
|
/// Backend / database error.
|
||||||
|
#[error("invoke backend error: {0}")]
|
||||||
|
Unavailable(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Test-only stub: every call errors. Lets harnesses build a `Services`
|
||||||
|
/// bundle without wiring an `InvokeService`.
|
||||||
|
#[derive(Debug, Default, Clone, Copy)]
|
||||||
|
pub struct NoopInvokeService;
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
impl InvokeService for NoopInvokeService {
|
||||||
|
async fn resolve(
|
||||||
|
&self,
|
||||||
|
_cx: &SdkCallCx,
|
||||||
|
_target: InvokeTarget,
|
||||||
|
) -> Result<ResolvedScript, InvokeError> {
|
||||||
|
Err(InvokeError::Unavailable("invoke is not wired in".into()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn enqueue_async(
|
||||||
|
&self,
|
||||||
|
_cx: &SdkCallCx,
|
||||||
|
_target: InvokeTarget,
|
||||||
|
_args: serde_json::Value,
|
||||||
|
) -> Result<ExecutionId, InvokeError> {
|
||||||
|
Err(InvokeError::Unavailable("invoke is not wired in".into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ pub mod files;
|
|||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod ids;
|
pub mod ids;
|
||||||
pub mod inbox;
|
pub mod inbox;
|
||||||
|
pub mod invoke;
|
||||||
pub mod kv;
|
pub mod kv;
|
||||||
pub mod log_sink;
|
pub mod log_sink;
|
||||||
pub mod modules;
|
pub mod modules;
|
||||||
@@ -60,6 +61,7 @@ pub use ids::{
|
|||||||
pub use inbox::{
|
pub use inbox::{
|
||||||
InboxDeliveryOutcome, InboxFailureKind, InboxResolver, InboxResult, NoopInboxResolver,
|
InboxDeliveryOutcome, InboxFailureKind, InboxResolver, InboxResult, NoopInboxResolver,
|
||||||
};
|
};
|
||||||
|
pub use invoke::{InvokeError, InvokeService, InvokeTarget, NoopInvokeService, ResolvedScript};
|
||||||
pub use kv::{KvError, KvListPage, KvService, NoopKvService};
|
pub use kv::{KvError, KvListPage, KvService, NoopKvService};
|
||||||
pub use log_sink::{ExecutionLogSink, LogSinkError};
|
pub use log_sink::{ExecutionLogSink, LogSinkError};
|
||||||
pub use modules::{ModuleScript, ModuleSource, ModuleSourceError, NoopModuleSource};
|
pub use modules::{ModuleScript, ModuleSource, ModuleSourceError, NoopModuleSource};
|
||||||
|
|||||||
@@ -20,11 +20,11 @@
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
DeadLetterService, DocsService, EmailService, FilesService, HttpService, KvService,
|
DeadLetterService, DocsService, EmailService, FilesService, HttpService, InvokeService,
|
||||||
ModuleSource, NoopDeadLetterService, NoopDocsService, NoopEmailService, NoopEventEmitter,
|
KvService, ModuleSource, NoopDeadLetterService, NoopDocsService, NoopEmailService,
|
||||||
NoopFilesService, NoopHttpService, NoopKvService, NoopModuleSource, NoopPubsubService,
|
NoopEventEmitter, NoopFilesService, NoopHttpService, NoopInvokeService, NoopKvService,
|
||||||
NoopQueueService, NoopSecretsService, NoopUsersService, PubsubService, QueueService,
|
NoopModuleSource, NoopPubsubService, NoopQueueService, NoopSecretsService, NoopUsersService,
|
||||||
SecretsService, ServiceEventEmitter, UsersService,
|
PubsubService, QueueService, SecretsService, ServiceEventEmitter, UsersService,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// SDK service bundle. See module docs for the lifecycle and the v1.1.x
|
/// SDK service bundle. See module docs for the lifecycle and the v1.1.x
|
||||||
@@ -102,6 +102,11 @@ pub struct Services {
|
|||||||
/// touch queues. Consumers register via `queue:receive` triggers
|
/// touch queues. Consumers register via `queue:receive` triggers
|
||||||
/// (one per `(app_id, queue_name)`).
|
/// (one per `(app_id, queue_name)`).
|
||||||
pub queue: Arc<dyn QueueService>,
|
pub queue: Arc<dyn QueueService>,
|
||||||
|
|
||||||
|
/// Same-app function composition (v1.1.9). Backs `invoke()` (sync)
|
||||||
|
/// and `invoke_async()` (fire-and-forget through the outbox).
|
||||||
|
/// Cross-app invokes are rejected at the service entry point.
|
||||||
|
pub invoke: Arc<dyn InvokeService>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Services {
|
impl Services {
|
||||||
@@ -123,6 +128,7 @@ impl Services {
|
|||||||
email: Arc<dyn EmailService>,
|
email: Arc<dyn EmailService>,
|
||||||
users: Arc<dyn UsersService>,
|
users: Arc<dyn UsersService>,
|
||||||
queue: Arc<dyn QueueService>,
|
queue: Arc<dyn QueueService>,
|
||||||
|
invoke: Arc<dyn InvokeService>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
kv,
|
kv,
|
||||||
@@ -137,6 +143,7 @@ impl Services {
|
|||||||
email,
|
email,
|
||||||
users,
|
users,
|
||||||
queue,
|
queue,
|
||||||
|
invoke,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +167,7 @@ impl Services {
|
|||||||
Arc::new(NoopEmailService),
|
Arc::new(NoopEmailService),
|
||||||
Arc::new(NoopUsersService),
|
Arc::new(NoopUsersService),
|
||||||
Arc::new(NoopQueueService),
|
Arc::new(NoopQueueService),
|
||||||
|
Arc::new(NoopInvokeService),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user