Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 5 additions & 1 deletion .config/hakari.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,11 @@ platforms = [
#
# `nexus-nostd-smoketest` is the no_std derive-macro compile-gate (#304): it is
# built for thumbv7em/wasm32, so a std workspace-hack edge would break it.
workspace-members = ["nexus", "nexus-nostd-smoketest"]
#
# `nexus-store` is no_std (core+alloc) — workspace-hack force-enables
# std-implying futures-util features (io/channel/sink) that feature
# unification would leak into its --no-default-features build. (#301)
workspace-members = ["nexus", "nexus-nostd-smoketest", "nexus-store"]
third-party = [
{ name = "futures-core" },
]
7 changes: 6 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,15 @@ members = [
]

[workspace.dependencies]
aligned-vec = "=0.6.4"
aligned-vec = { version = "=0.6.4", default-features = false }
arrayvec = { version = "0.7.6", default-features = false }
bytemuck = { version = "1", features = ["derive"] }
bytes = "1"
bytes = { version = "1", default-features = false }
crc32c = "0.6.8"
criterion = { version = "0.8", features = ["html_reports"] }
fjall = { version = "3", features = ["bytes_1"] }
foldhash = "0.2.0"
futures = "0.3"
futures = { version = "0.3", default-features = false, features = ["alloc"] }
futures-core = { version = "0.3", default-features = false }
insta = "1.47.2"
minicbor = { version = "2.2.2", features = ["alloc"] }
Expand Down
4 changes: 2 additions & 2 deletions crates/nexus-fjall/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ import = ["nexus-store/import"]

[dependencies]
arrayvec = { workspace = true }
bytes = { workspace = true }
bytes = { workspace = true, features = ["std"] }
fjall = { workspace = true }
futures.workspace = true
futures = { workspace = true, features = ["std", "async-await", "executor"] }
nexus = { version = "0.1.0", path = "../nexus" }
nexus-store = { version = "0.1.0", path = "../nexus-store", features = ["subscription"] }
nexus-wake = { version = "0.1.0", path = "../nexus-wake" }
Expand Down
4 changes: 2 additions & 2 deletions crates/nexus-inmemory/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ export = ["nexus-store/export"]
import = ["nexus-store/import"]

[dependencies]
bytes = { workspace = true }
futures = { workspace = true }
bytes = { workspace = true, features = ["std"] }
futures = { workspace = true, features = ["std", "async-await", "executor"] }
nexus = { version = "0.1.0", path = "../nexus" }
nexus-store = { version = "0.1.0", path = "../nexus-store", features = ["subscription"] }
nexus-wake = { version = "0.1.0", path = "../nexus-wake" }
Expand Down
4 changes: 2 additions & 2 deletions crates/nexus-postgres/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ keywords = ["event-sourcing", "event-store", "postgres"]
categories = ["database"]

[dependencies]
bytes = { workspace = true }
futures = { workspace = true }
bytes = { workspace = true, features = ["std"] }
futures = { workspace = true, features = ["std", "async-await", "executor"] }
nexus = { version = "0.1.0", path = "../nexus" }
nexus-store = { version = "0.1.0", path = "../nexus-store", features = ["subscription"] }
nexus-wake = { version = "0.1.0", path = "../nexus-wake" }
Expand Down
2 changes: 1 addition & 1 deletion crates/nexus-store-testing/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ keywords = ["event-sourcing", "testing", "conformance"]
categories = ["data-structures", "development-tools::testing"]

[dependencies]
futures.workspace = true
futures = { workspace = true, features = ["std", "async-await", "executor"] }
nexus = { version = "0.1.0", path = "../nexus" }
nexus-store = { version = "0.1.0", path = "../nexus-store" }
tokio = { workspace = true, features = ["macros", "rt"] }
Expand Down
18 changes: 14 additions & 4 deletions crates/nexus-store/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,17 @@ keywords = ["event-sourcing", "event-store", "cqrs", "persistence"]
categories = ["data-structures"]

[features]
default = ["std"]
# Additive: enabling `std` only ADDS (the std::error::Error bridge via
# thiserror + the deps' std impls); disabling it (`--no-default-features`)
# yields a no_std + alloc + core::error::Error store. (#301, mirrors #279)
std = [
"thiserror/std",
"nexus/std",
"futures/std",
"bytes/std",
"aligned-vec/std",
]
# Generic catch-up-then-live-tail subscription loop + the `wake` traits.
# Dep-free: the loop is generic over `WakeSource`, so no runtime is pulled.
# The in-process wake registry (`StreamNotifiers`, tokio-backed) lives in the
Expand Down Expand Up @@ -49,16 +60,15 @@ crc32c = { workspace = true, optional = true }
futures = { workspace = true }
futures-core = { workspace = true }
minicbor = { workspace = true, features = ["derive"], optional = true }
nexus = { version = "0.1.0", path = "../nexus" }
nexus = { version = "0.1.0", path = "../nexus", default-features = false }
rkyv = { workspace = true, optional = true }
serde = { workspace = true, optional = true }
serde_json = { workspace = true, optional = true }
thiserror = { workspace = true, features = ["std"] }
workspace-hack = { version = "0.1", path = "../workspace-hack" }
thiserror = { workspace = true }

[dev-dependencies]
criterion = { workspace = true }
futures = { workspace = true }
futures = { workspace = true, features = ["std", "async-await", "executor"] }
insta = { workspace = true }
# Self dev-dependency unifies feature-gated integration tests into the
# default-feature nextest gate (which builds no explicit features). Without
Expand Down
10 changes: 5 additions & 5 deletions crates/nexus-store/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::marker::PhantomData;
use core::marker::PhantomData;

use crate::repository::EventStore;
use crate::store::{RawEventStore, Store};
Expand Down Expand Up @@ -44,7 +44,7 @@ pub struct NoSnapshot;
pub struct WithSnapshot<SS, T> {
store: SS,
trigger: T,
schema_version: std::num::NonZeroU32,
schema_version: core::num::NonZeroU32,
snapshot_on_read: bool,
}

Expand Down Expand Up @@ -152,15 +152,15 @@ use super::snapshot::Snapshotting;
#[cfg(feature = "snapshot")]
use crate::state;
#[cfg(feature = "snapshot")]
use std::num::NonZeroU64;
use core::num::NonZeroU64;

/// Default snapshot interval.
#[cfg(feature = "snapshot")]
const DEFAULT_SNAPSHOT_INTERVAL: u64 = 100;

/// Default snapshot schema version.
#[cfg(feature = "snapshot")]
const DEFAULT_SCHEMA_VERSION: std::num::NonZeroU32 = std::num::NonZeroU32::MIN;
const DEFAULT_SCHEMA_VERSION: core::num::NonZeroU32 = core::num::NonZeroU32::MIN;

#[cfg(feature = "snapshot-json")]
impl<S, C, A> RepositoryBuilder<S, C, A, NoSnapshot> {
Expand Down Expand Up @@ -292,7 +292,7 @@ impl<S, C, A, SS, T> RepositoryBuilder<S, C, A, WithSnapshot<SS, T>> {

/// Set the schema version for snapshot invalidation.
#[must_use]
pub const fn snapshot_schema_version(mut self, version: std::num::NonZeroU32) -> Self {
pub const fn snapshot_schema_version(mut self, version: core::num::NonZeroU32) -> Self {
self.snapshot.schema_version = version;
self
}
Expand Down
2 changes: 1 addition & 1 deletion crates/nexus-store/src/catchup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
//! [`subscription_cursor`](crate::subscription_cursor), which the user-facing
//! [`Subscription`](crate::Subscription) assembles per call site.

use alloc::sync::Arc;
use core::future::Future;
use std::sync::Arc;

use futures::StreamExt;
use nexus::Version;
Expand Down
1 change: 1 addition & 0 deletions crates/nexus-store/src/cbor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//! followed by block arrays `[crc32c, body]`. See
//! `docs/plans/2026-06-21-export-import-cbor-box-design.md`.

use alloc::vec::Vec;
use core::convert::Infallible;
use core::ops::Range;

Expand Down
22 changes: 13 additions & 9 deletions crates/nexus-store/src/codec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
//! ```ignore
//! pub trait Decode<E: ?Sized>: Send + Sync + 'static {
//! type Output<'a> where Self: 'a;
//! type Error: std::error::Error + Send + Sync + 'static;
//! type Error: core::error::Error + Send + Sync + 'static;
//! fn decode<'a>(&'a self, env: &'a PersistedEnvelope)
//! -> Result<Self::Output<'a>, Self::Error>;
//! }
Expand Down Expand Up @@ -53,7 +53,7 @@ use crate::envelope::PersistedEnvelope;
/// `E: ?Sized` allows unsized event types (e.g. `Archived<MyEvent>`).
pub trait Encode<E: ?Sized>: Send + Sync + 'static {
/// The error type for serialization failures.
type Error: std::error::Error + Send + Sync + 'static;
type Error: core::error::Error + Send + Sync + 'static;

/// Serialize a typed value to bytes.
///
Expand Down Expand Up @@ -111,7 +111,7 @@ pub trait Decode<E: ?Sized>: Send + Sync + 'static {
Self: 'a;

/// The error type for deserialization failures.
type Error: std::error::Error + Send + Sync + 'static;
type Error: core::error::Error + Send + Sync + 'static;

/// Decode the envelope's payload to [`Output<'a>`](Self::Output).
///
Expand Down Expand Up @@ -152,6 +152,8 @@ impl<C, E: ?Sized> OwningCodec<E> for C where C: for<'a> Decode<E, Output<'a> =

#[cfg(feature = "serde")]
pub mod serde {
use alloc::vec::Vec;

use ::serde::{Serialize, de::DeserializeOwned};

use super::{Decode, Encode};
Expand All @@ -170,7 +172,7 @@ pub mod serde {
/// - Errors must accurately describe the failure (not erase the cause).
pub trait SerdeFormat: Send + Sync + 'static {
/// The error type for serialization/deserialization failures.
type Error: std::error::Error + Send + Sync + 'static;
type Error: core::error::Error + Send + Sync + 'static;

/// Serialize a value to bytes.
///
Expand Down Expand Up @@ -260,16 +262,18 @@ pub mod serde {
}

/// Seal `Debug` — show the format type, not its internals.
impl<F> std::fmt::Debug for SerdeCodec<F> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
impl<F> core::fmt::Debug for SerdeCodec<F> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("SerdeCodec")
.field("format", &std::any::type_name::<F>())
.field("format", &core::any::type_name::<F>())
.finish()
}
}

#[cfg(feature = "json")]
pub mod json {
use alloc::vec::Vec;

use ::serde::{Serialize, de::DeserializeOwned};

use super::{SerdeCodec, SerdeFormat};
Expand Down Expand Up @@ -310,9 +314,9 @@ pub mod bytemuck {
use super::{Decode, Encode};
use crate::envelope::PersistedEnvelope;

/// Wrapper around [`PodCastError`] that satisfies the `std::error::Error` bound.
/// Wrapper around [`PodCastError`] that satisfies the `core::error::Error` bound.
///
/// Upstream `PodCastError` does not implement `std::error::Error`
/// Upstream `PodCastError` does not implement `core::error::Error`
/// (`bytemuck` is `no_std` by default and skips the impl), so
/// `Decode::Error` requires a wrapper.
///
Expand Down
10 changes: 5 additions & 5 deletions crates/nexus-store/src/envelope.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::ops::Range;
use core::ops::Range;

use bytes::Bytes;
use nexus::{DomainEvent, Version};
Expand Down Expand Up @@ -37,7 +37,7 @@ pub enum EnvelopeError {
start: u32,
end: u32,
#[source]
source: std::str::Utf8Error,
source: core::str::Utf8Error,
},

/// The `event_type` range length exceeds the wire-format cap.
Expand Down Expand Up @@ -397,7 +397,7 @@ impl PersistedEnvelope {
// UTF-8 validation of event_type once at construction.
let et_start = idx(event_type_range.start);
let et_end = idx(event_type_range.end);
std::str::from_utf8(&value[et_start..et_end]).map_err(|e| EnvelopeError::InvalidUtf8 {
core::str::from_utf8(&value[et_start..et_end]).map_err(|e| EnvelopeError::InvalidUtf8 {
start: event_type_range.start,
end: event_type_range.end,
source: e,
Expand Down Expand Up @@ -443,7 +443,7 @@ impl PersistedEnvelope {
reason = "UTF-8 invariant established at construction; ranges validated"
)]
unsafe {
std::str::from_utf8_unchecked(&self.value[start..end])
core::str::from_utf8_unchecked(&self.value[start..end])
}
}

Expand Down Expand Up @@ -489,7 +489,7 @@ impl PersistedEnvelope {
pub fn event_type_value(&self) -> EventType {
// SAFETY: `from_validated_bytes` requires (1) valid UTF-8 and
// (2) `bytes.len() <= MAX_EVENT_TYPE_LEN`. Both invariants are
// established by `try_new`: UTF-8 via `std::str::from_utf8`, and
// established by `try_new`: UTF-8 via `core::str::from_utf8`, and
// the cap via the `EventTypeRangeTooLong` check on the range
// length.
#[allow(
Expand Down
6 changes: 3 additions & 3 deletions crates/nexus-store/src/export.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ pub trait StreamLister: RawEventStore {
/// guaranteed order, terminating when exhausted.
fn list_streams(
&self,
) -> impl std::future::Future<Output = Result<Self::StreamList, Self::Error>> + Send;
) -> impl core::future::Future<Output = Result<Self::StreamList, Self::Error>> + Send;
}

/// Export a single stream's events — a raw pass-through read.
Expand Down Expand Up @@ -93,7 +93,7 @@ pub trait EventExporter: RawEventStore {
&self,
id: &StreamKey,
from: Version,
) -> impl std::future::Future<Output = Result<Self::ExportStream, Self::Error>> + Send;
) -> impl core::future::Future<Output = Result<Self::ExportStream, Self::Error>> + Send;
}

/// Every [`RawEventStore`] is an [`EventExporter`] — export is just a read.
Expand All @@ -109,7 +109,7 @@ impl<S: RawEventStore> EventExporter for S {
&self,
id: &StreamKey,
from: Version,
) -> impl std::future::Future<Output = Result<Self::ExportStream, Self::Error>> + Send {
) -> impl core::future::Future<Output = Result<Self::ExportStream, Self::Error>> + Send {
self.read_stream(id, from)
}
}
Expand Down
7 changes: 5 additions & 2 deletions crates/nexus-store/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@
//! the report, the error) and the [`EventImporter`] trait. The concrete
//! ingest impl is a later card.

use alloc::vec;
use alloc::vec::Vec;

use bytes::Bytes;
use nexus::Version;
use thiserror::Error;
Expand Down Expand Up @@ -265,7 +268,7 @@ pub trait AtomicAppend: RawEventStore {
fn atomic_append_many(
&self,
writes: &[PlannedAppend],
) -> impl std::future::Future<Output = Result<(), AtomicAppendError<Self::Error>>> + Send;
) -> impl core::future::Future<Output = Result<(), AtomicAppendError<Self::Error>>> + Send;
}

/// `Store<S>` forwards [`AtomicAppend`] to its inner backend (issue #247). With
Expand Down Expand Up @@ -308,7 +311,7 @@ pub trait EventImporter: RawEventStore + AtomicAppend {
sections: &[StreamSection],
route: R,
atomicity: Atomicity,
) -> impl std::future::Future<Output = Result<ImportReport, ImportError<Self::Error>>> + Send
) -> impl core::future::Future<Output = Result<ImportReport, ImportError<Self::Error>>> + Send
where
R: Fn(&[u8]) -> StreamKey + Send;
}
Expand Down
6 changes: 6 additions & 0 deletions crates/nexus-store/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@
//! (kernel-pure → store-persistence → adapters); the boundary that
//! didn't matter was inside `nexus-store`.

#![cfg_attr(not(feature = "std"), no_std)]

// The store is alloc-dependent by design (Bytes, Vec, Arc are its working
// vocabulary) — unlike the pure-core kernel, `alloc` is unconditional.
extern crate alloc;

pub mod batch;
pub mod builder;
#[cfg(feature = "subscription")]
Expand Down
Loading
Loading