Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dcb97bf
feat(e2e-ui): framework home + How it’s built slide-out
patrickleet Jul 31, 2026
945da30
fix(e2e-ui): hero highlights full-stack CQRS, TS, OIDC, SvelteKit
patrickleet Jul 31, 2026
d61a478
fix(e2e-ui): wider How-it’s-built panel, no closed shadow, code colors
patrickleet Jul 31, 2026
d2674e5
feat(e2e-ui): browser-first How-it’s-built tab order
patrickleet Jul 31, 2026
f77fa6c
feat(e2e-ui): open lobby chat for anonymous GraphQL reads
patrickleet Aug 1, 2026
62840ca
feat(e2e-ui): show RBAC on How-it’s-built query and command tabs
patrickleet Aug 1, 2026
637c3c3
fix(e2e-ui): replace comment-only How-it’s-built samples with real code
patrickleet Aug 1, 2026
ca67858
feat(e2e-ui): rewrite home as Distributed product landing
patrickleet Aug 1, 2026
d11aa62
style(e2e-ui): product-home layout styles for dist-* sections
patrickleet Aug 1, 2026
bbb37ff
fix(e2e-ui): reframe home as GraphQL realtime → owned write model
patrickleet Aug 1, 2026
7216649
fix(e2e-ui): home is CQRS/domain-first; GraphQL is transport
patrickleet Aug 1, 2026
1478a08
fix(e2e-ui): brand Distributed; hero product definition
patrickleet Aug 1, 2026
01858e2
fix(e2e-ui): hero — start simple, scale to microservices
patrickleet Aug 1, 2026
71c2ea8
feat(e2e-ui): product home Features from CQRS/ES canon narrative
patrickleet Aug 1, 2026
dd7cffe
refactor: rename portable_handlers! to projection!
patrickleet Aug 1, 2026
b0030b6
feat: event-first projection! on { events, mutation, input }
patrickleet Aug 1, 2026
4cc52dc
refactor(e2e-ui): name replica handles query, data by resource
patrickleet Aug 1, 2026
19ca6dd
feat(e2e-ui): SOTA home story — claim arc, general bar, delivery
patrickleet Aug 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/graphql/command_contract/typed_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -637,7 +637,7 @@ impl<I, K: CommandOutcome> TypedCommand<I, K> {
/// Declare known mutation-input fields for client cache application.
///
/// Maps command-known values (and unknowns) onto the emitted domain-event
/// body shape that portable handlers bind into mutation IR. The client
/// body shape that projections bind into mutation IR. The client
/// applies that mutation to the cache only; the server applies the same
/// mutation for real (eventual projector or handler-owned projected row).
///
Expand Down
217 changes: 76 additions & 141 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -80,135 +80,72 @@ pub use projection::{
MAX_PROJECTION_OPERATIONS_PER_OCCURRENCE, MAX_PROJECTION_PATH_SEGMENTS,
};

// Event-independent mutation IR, portable handlers, and interpreters.
// Event-independent mutation IR, event→mutation projections, and interpreters.
pub use mutation::{
bind_delete_to_envelope_id, bind_event_to_mutation, bind_state_body_to_mutation,
bind_state_events_to_mutation, body_bindings_for_model, body_field_binding,
compile_portable_handlers, compose_event_preview, delete_by_pk_program_for_model,
bind_delete_to_envelope_id, bind_event_apply_mutation, bind_event_to_mutation,
bind_state_body_to_mutation, bind_state_events_to_mutation, body_bindings_for_model,
body_field_binding, compile_projection, compose_event_preview, delete_by_pk_program_for_model,
descriptor_from_factories, envelope_binding, inventory_single_model, lower_mutation_cache,
lower_single_model, portable_binding, resolve_mutation_program, state_upsert_program_for_model,
Mutation, MutationAssignment, MutationCacheEffect, MutationCacheProgram,
MutationCacheVisibility, MutationConflictTarget, MutationEventBinding, MutationExpression,
MutationField, MutationFieldCapability, MutationHandlerCatalog, MutationHandlerPlacement,
MutationHandlerRegistration, MutationInputBinding, MutationKeyCapability, MutationKeyField,
MutationKind, MutationOperation, MutationProgram, MutationProgramError, MutationProgramId,
MutationProgramLimits, MutationReturning, MutationServerInterpreter, PortableHandler,
ReadModelMutationCapabilities, ResolvedMutationValue, MAX_MUTATION_OPERATIONS,
MUTATION_OPERATION_SEMANTICS_VERSION, MUTATION_PROGRAM_IR_VERSION,
MutationProgramLimits, MutationReturning, MutationServerInterpreter, ProjectionHandler,
ProjectionInputSource, ReadModelMutationCapabilities, ResolvedMutationValue,
MAX_MUTATION_OPERATIONS, MUTATION_OPERATION_SEMANTICS_VERSION, MUTATION_PROGRAM_IR_VERSION,
};

/// Spec-shaped authoring: **mutations** + **event-first portable handlers**.
/// Spec-shaped authoring: **mutations** + **event-first projections**.
///
/// Declare which domain events apply which mutation (`on <events> apply …`).
/// The framework compiles that into the service mount. Dual-path projection IR
/// is an internal detail.
/// One or more `on { events, mutation, input }` arms. `input` declares how the
/// occurrence fills mutation inputs (`body` = event body object root;
/// `aggregate_id` = envelope id for delete-by-pk).
///
/// ```ignore
/// portable_handlers! {
/// pub const BLOB_GAMES: ProjectionDescriptor<DirectCandidate> = {
/// name: "project_blob",
/// projection! {
/// pub const TODOS: ProjectionDescriptor<EventualOnly> = {
/// name: "project_todos",
/// version: 1,
/// epoch: "e2e-ui-blob-v2",
/// model: BlobGames,
/// on_state BlobGameState as "game" apply save_blob_game {
/// "blob.initialized", "blob.moved",
/// }
/// epoch: "e2e-ui-todos-v2",
/// model: Todos,
/// on {
/// events: [
/// TodoCreatedDomainEvent,
/// TodoCompletedDomainEvent,
/// ],
/// mutation: save_todo,
/// input: { todo: body },
/// },
/// on {
/// events: [TodoPurgedDomainEvent],
/// mutation: delete_todo,
/// input: { todo_id: aggregate_id },
/// },
/// };
/// }
/// ```
///
/// A `program:` arm remains as an escape hatch for custom partition / multi-binding
/// factories. Prefer the event-first form when partition is unit.
#[macro_export]
macro_rules! portable_handlers {
// Event-first: state-body events by name + DomainState type.
macro_rules! projection {
// Event-first: one or more `on { events, mutation, input }` arms.
(
$vis:vis const $id:ident : $desc_ty:ty = {
name: $name:literal,
version: $version:expr,
epoch: $epoch:literal,
model: $model:ty,
on_state $state:ty as $input_root:literal apply $mutation:path {
$($event:literal),+ $(,)?
}
$(,)?
} $(;)?
) => {
$vis const $id: $desc_ty = {
fn __handlers() -> ::core::result::Result<
$crate::ProjectionProgram,
$crate::ProjectionProgramError,
> {
let mutation = $mutation().program().clone();
let mut handlers = ::std::vec::Vec::new();
$(
{
let descriptor =
$crate::DomainEventDescriptor::state::<$state>($event, 1);
handlers.push(
$crate::bind_state_body_to_mutation::<$model>(
&descriptor,
mutation.clone(),
$input_root,
)
.map_err(|e| $crate::ProjectionProgramError::InvalidOperation {
operation: ::std::string::String::from($name),
reason: e.to_string(),
})?,
);
}
)+
$crate::compile_portable_handlers(
$name,
$version,
$crate::ProjectionPartition::Unit,
handlers,
)
}
fn __resolve(
occurrence: &$crate::DomainEventOccurrence,
) -> ::core::result::Result<
$crate::ResolvedProjectionPlan,
$crate::ProjectionProgramError,
> {
$crate::resolve_mutation_program(&__handlers()?, occurrence)
}
fn __lower(
plan: &$crate::ResolvedProjectionPlan,
) -> ::core::result::Result<
$crate::projection::lower::LoweredProjectionPlan,
$crate::projection::lower::ProjectionLoweringError,
> {
$crate::lower_single_model::<$model>(plan)
}
fn __inventory() -> ::core::result::Result<
$crate::projection::lower::ProjectionOutputInventory,
$crate::projection::lower::ProjectionLoweringError,
> {
$crate::inventory_single_model::<$model>()
}
$crate::descriptor_from_factories(
$name,
$version,
$epoch,
__handlers,
__resolve,
__lower,
__inventory,
)
};
};

// Event-first: DomainEventContract types + optional delete binding.
(
$vis:vis const $id:ident : $desc_ty:ty = {
name: $name:literal,
version: $version:expr,
epoch: $epoch:literal,
model: $model:ty,
on_event { $($event_ty:ty),+ $(,)? }
apply $mutation:path as $input_root:literal
$(
, on_deleted $del_event:ty => apply $del_mutation:path as $pk:literal
)?
$(,)?
on {
events: [ $($event_ty:ty),+ $(,)? ],
mutation: $mutation:path,
input: { $input_key:ident : $input_src:ident },
$(,)?
}
),+ $(,)?
} $(;)?
) => {
$vis const $id: $desc_ty = {
Expand All @@ -217,39 +154,28 @@ macro_rules! portable_handlers {
$crate::ProjectionProgramError,
> {
use $crate::domain_event::DomainEventContract;
let mutation = $mutation().program().clone();
let mut handlers = ::std::vec::Vec::new();
$(
{
handlers.push(
$crate::bind_state_body_to_mutation::<$model>(
&<$event_ty>::descriptor(),
mutation.clone(),
$input_root,
)
.map_err(|e| $crate::ProjectionProgramError::InvalidOperation {
operation: ::std::string::String::from($name),
reason: e.to_string(),
})?,
);
let mutation = $mutation().program().clone();
let input_source = $crate::__projection_input_source!($input_src);
$(
handlers.push(
$crate::bind_event_apply_mutation::<$model>(
&<$event_ty>::descriptor(),
mutation.clone(),
::core::stringify!($input_key),
input_source,
)
.map_err(|e| $crate::ProjectionProgramError::InvalidOperation {
operation: ::std::string::String::from($name),
reason: e.to_string(),
})?,
);
)+
}
)+
$(
{
handlers.push(
$crate::bind_delete_to_envelope_id(
&<$del_event>::descriptor(),
$del_mutation().program().clone(),
$pk,
)
.map_err(|e| $crate::ProjectionProgramError::InvalidOperation {
operation: ::std::string::String::from($name),
reason: e.to_string(),
})?,
);
}
)?
$crate::compile_portable_handlers(
$crate::compile_projection(
$name,
$version,
$crate::ProjectionPartition::Unit,
Expand Down Expand Up @@ -289,13 +215,9 @@ macro_rules! portable_handlers {
)
};
};
}

/// Lower-level mount when handlers need a custom partition (e.g. chat room_id).
///
/// Prefer [`portable_handlers!`] when partition is unit.
#[macro_export]
macro_rules! mutation_projector {
// Escape hatch: custom program factory (partition / multi-binding / etc.).
// Prefer the event-first arms above when partition is unit.
(
$vis:vis const $id:ident : $desc_ty:ty = {
name: $name:literal,
Expand Down Expand Up @@ -347,6 +269,18 @@ macro_rules! mutation_projector {
};
}

/// Map `input: { key: body | aggregate_id }` keywords for [`projection!`].
#[macro_export]
#[doc(hidden)]
macro_rules! __projection_input_source {
(body) => {
$crate::ProjectionInputSource::Body
};
(aggregate_id) => {
$crate::ProjectionInputSource::AggregateId
};
}

pub type SourcedResult<T = ()> = std::result::Result<T, EventRecordError>;

// Re-export repository traits at crate root for convenience
Expand Down Expand Up @@ -472,9 +406,10 @@ macro_rules! graphql_models {
// Session convenience re-exports used by GraphQL permission filters.
pub use microsvc::{ROLE_KEY, USER_ID_KEY};

// Re-export proc macros (event-owning `projection!` and separately authored
// `command_effects!` / `command_confirmations!` removed — use `mutation!` +
// portable/modeled handlers; commands predict events via `.emits`/`.preview`).
// Re-export proc macros. The old event-owning projection proc-macro and
// separately authored `command_effects!` / `command_confirmations!` are gone.
// Use `mutation!` / `mutation_file!` + declarative `projection!` (event→mutation
// mount); commands predict events via `.emits`/`.preview`.
pub use distributed_macros::{
aggregate, command_input_defaults, digest, mutation, mutation_file, sourced, DomainEvent,
DomainState,
Expand Down
8 changes: 4 additions & 4 deletions src/microsvc/causal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -899,8 +899,8 @@ mod tests {

fn modeled_direct_program() -> Result<crate::ProjectionProgram, crate::ProjectionProgramError> {
use crate::mutation::{
body_bindings_for_model, compile_portable_handlers, state_upsert_program_for_model,
MutationEventBinding, PortableHandler,
body_bindings_for_model, compile_projection, state_upsert_program_for_model,
MutationEventBinding, ProjectionHandler,
};
use crate::projection::ProjectionEventSelector;

Expand Down Expand Up @@ -932,11 +932,11 @@ mod tests {
operation: "modeled-direct".into(),
reason: e.to_string(),
})?;
compile_portable_handlers(
compile_projection(
"modeled-direct-workspace",
1,
crate::ProjectionPartition::Unit,
[PortableHandler::from_binding("created", binding)],
[ProjectionHandler::from_binding("created", binding)],
)
.map_err(|e| crate::ProjectionProgramError::InvalidOperation {
operation: "modeled-direct".into(),
Expand Down
8 changes: 4 additions & 4 deletions src/microsvc/projector/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ struct MalformedView {
fn generated_modeled_projector_program(
) -> Result<crate::ProjectionProgram, crate::ProjectionProgramError> {
use crate::mutation::{
body_field_binding, compile_portable_handlers, MutationAssignment, MutationConflictTarget,
body_field_binding, compile_projection, MutationAssignment, MutationConflictTarget,
MutationEventBinding, MutationExpression, MutationField, MutationKeyField, MutationKind,
MutationOperation, MutationProgram, PortableHandler,
MutationOperation, MutationProgram, ProjectionHandler,
};
use crate::projection::{
ProjectionEventSelector, ProjectionPartition, ProjectionTarget, ProjectionValueType,
Expand Down Expand Up @@ -148,11 +148,11 @@ fn generated_modeled_projector_program(
reason: e.to_string(),
}
})?;
compile_portable_handlers(
compile_projection(
"task11-generated-multi-table",
1,
ProjectionPartition::Unit,
[PortableHandler::from_binding("changed", binding)],
[ProjectionHandler::from_binding("changed", binding)],
)
.map_err(|e| crate::ProjectionProgramError::InvalidOperation {
operation: "task11-generated-multi-table".into(),
Expand Down
12 changes: 6 additions & 6 deletions src/microsvc/service/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ fn causal_direct_program(
event_name: &'static str,
) -> Result<crate::ProjectionProgram, crate::ProjectionProgramError> {
use crate::mutation::{
bind_state_body_to_mutation, compile_portable_handlers, state_upsert_program_for_model,
bind_state_body_to_mutation, compile_projection, state_upsert_program_for_model,
};

let program = state_upsert_program_for_model::<CausalProjectionObligationView>(
Expand All @@ -213,7 +213,7 @@ fn causal_direct_program(
operation: name.into(),
reason: e.to_string(),
})?;
compile_portable_handlers(name, version, crate::ProjectionPartition::Unit, [handler]).map_err(
compile_projection(name, version, crate::ProjectionPartition::Unit, [handler]).map_err(
|e| crate::ProjectionProgramError::InvalidOperation {
operation: name.into(),
reason: e.to_string(),
Expand Down Expand Up @@ -314,7 +314,7 @@ const CAUSAL_ROGUE_DIRECT_PROJECTION: crate::projection::lower::ProjectionDescri
#[cfg(feature = "graphql")]
fn causal_sibling_program() -> Result<crate::ProjectionProgram, crate::ProjectionProgramError> {
use crate::mutation::{
bind_state_body_to_mutation, compile_portable_handlers, state_upsert_program_for_model,
bind_state_body_to_mutation, compile_projection, state_upsert_program_for_model,
};

let program = state_upsert_program_for_model::<CausalProjectionSiblingView>(
Expand All @@ -340,7 +340,7 @@ fn causal_sibling_program() -> Result<crate::ProjectionProgram, crate::Projectio
operation: "project_causal_direct_sibling".into(),
reason: e.to_string(),
})?;
compile_portable_handlers(
compile_projection(
"project_causal_direct_sibling",
1,
crate::ProjectionPartition::Unit,
Expand Down Expand Up @@ -394,7 +394,7 @@ const CAUSAL_SIBLING_DIRECT_PROJECTION: crate::projection::lower::ProjectionDesc
fn causal_lifecycle_program() -> Result<crate::ProjectionProgram, crate::ProjectionProgramError> {
use crate::domain_event::DomainEventContract;
use crate::mutation::{
bind_state_body_to_mutation, compile_portable_handlers, state_upsert_program_for_model,
bind_state_body_to_mutation, compile_projection, state_upsert_program_for_model,
};

let program = state_upsert_program_for_model::<CausalLifecycleView>(
Expand All @@ -416,7 +416,7 @@ fn causal_lifecycle_program() -> Result<crate::ProjectionProgram, crate::Project
operation: "project_causal_lifecycle".into(),
reason: e.to_string(),
})?;
compile_portable_handlers(
compile_projection(
"project_causal_lifecycle",
1,
crate::ProjectionPartition::Unit,
Expand Down
Loading
Loading