Skip to main content

thingd/
lib.rs

1//! Core primitives for thingd.
2//!
3//! This crate owns the durable engine boundary: object storage, append-only
4//! events, and queue storage. The default implementation is in-memory, with a
5//! feature-gated `SQLite` adapter available for durable object, event, and
6//! queue storage.
7//!
8//! # Feature Flags
9//!
10//! | Feature | Default | Description |
11#![allow(
12    clippy::cast_possible_wrap,
13    clippy::cast_precision_loss,
14    clippy::cast_sign_loss,
15    clippy::too_many_lines,
16    clippy::option_if_let_else,
17    clippy::manual_let_else
18)]
19//! |---------|---------|-------------|
20//! | `sqlite` | No | Enables [`SqliteThingStore`] with FTS5 search, WAL mode, and auto-migration |
21//! | `connectors` | No | Enables CSV/JSON file connectors for data import |
22//!
23//! # Example (in-memory)
24//!
25//! ```rust
26//! use thingd::{MemoryEngine, ObjectStore, EventLog, MemoryObject, MemoryEvent};
27//!
28//! let mut engine = MemoryEngine::new();
29//!
30//! let obj = MemoryObject::new("users", "alice", r#"{"name":"Alice"}"#);
31//! engine.put_object(obj).unwrap();
32//!
33//! let user = engine.get_object("users", "alice").unwrap();
34//! assert_eq!(user.unwrap().body, r#"{"name":"Alice"}"#);
35//!
36//! let event = MemoryEvent::new("audit", "user.created", r#"{"user":"alice"}"#);
37//! engine.append_event(event).unwrap();
38//! ```
39//!
40//! # Example (`SQLite` — requires `sqlite` feature)
41//!
42//! ```rust,no_run
43//! #[cfg(feature = "sqlite")]
44//! {
45//!     use thingd::{SqliteThingStore, ObjectStore, MemoryObject};
46//!
47//!     let mut db = SqliteThingStore::open_in_memory().unwrap();
48//!     db.put_object(MemoryObject::new("users", "alice", r#"{"name":"Alice"}"#)).unwrap();
49//!     let user = db.get_object("users", "alice").unwrap();
50//!     assert_eq!(user.unwrap().body, r#"{"name":"Alice"}"#);
51//! }
52//! ```
53
54#![forbid(unsafe_code)]
55#![warn(missing_docs)]
56#![cfg_attr(docsrs, feature(doc_cfg))]
57
58use std::time::{SystemTime, UNIX_EPOCH};
59
60#[cfg(feature = "connectors")]
61pub mod connector;
62#[cfg(feature = "connectors")]
63pub mod connectors;
64mod error;
65mod in_memory;
66mod model;
67#[cfg(feature = "sqlite")]
68mod sqlite;
69mod store;
70
71#[cfg(feature = "connectors")]
72pub use connector::{
73    Column, ColumnType, Connector, ConnectorAuth, ConnectorConfig, FileConnector, PullStream,
74    Schema, SslMode, SyncStrategy,
75};
76#[cfg(feature = "connectors")]
77pub use connectors::{MysqlConnector, PostgresConnector};
78pub use error::{ThingdError, ThingdResult};
79pub use in_memory::MemoryEngine;
80pub use model::{
81    AggregateFunction, AggregateGroupResult, AggregateOptions, AggregateResult, CollectionSchema,
82    DEFAULT_QUEUE_LEASE_MS, FieldSchema, Link, LinkDirection, LinkQueryOptions, ListEventsOptions,
83    ListObjectsOptions, MemoryEvent, MemoryObject, ObjectKey, PutObjectOptions, QueueClaimOptions,
84    QueueJob, QueueJobStatus, QueueNackOptions, SchemaOptions, SearchHit, SearchOptions, SortBy,
85    SortDirection, TimeBucket, TimeSeriesBucket, TimeSeriesOptions, TimeSeriesResult,
86};
87#[cfg(feature = "sqlite")]
88#[cfg_attr(docsrs, doc(cfg(feature = "sqlite")))]
89pub use sqlite::{SQLITE_SCHEMA_VERSION, SqliteThingStore};
90pub use store::{
91    AggregateStore, EventLog, LinkStore, ObjectStore, QueueStore, Searcher, ThingStore,
92};
93
94pub(crate) fn unix_timestamp_millis() -> i64 {
95    let Ok(duration) = SystemTime::now().duration_since(UNIX_EPOCH) else {
96        #[cfg(debug_assertions)]
97        panic!("SystemTime::now is before UNIX epoch — clock is broken");
98        #[cfg(not(debug_assertions))]
99        {
100            return 0;
101        }
102    };
103
104    i64::try_from(duration.as_millis()).unwrap_or(i64::MAX)
105}
106
107pub(crate) fn u64_to_i64(value: u64) -> i64 {
108    i64::try_from(value).unwrap_or(i64::MAX)
109}
110
111pub(crate) fn now_iso_string() -> String {
112    chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Millis, true)
113}