Skip to main content

icydb/
lib.rs

1//! Module: lib
2//!
3//! Responsibility: public facade crate surface and generated-code wiring.
4//! Does not own: core execution, storage internals, or schema mutation semantics.
5//! Boundary: re-exports stable runtime and generated actor-wiring surfaces.
6
7//! # icydb
8//!
9//! `icydb` is the **public facade crate** for the IcyDB runtime.
10//! It is the recommended dependency for downstream canister projects.
11//!
12//! This crate exposes:
13//! - the stable runtime surface used inside canister actor code,
14//! - accepted-schema-bound database request and response types,
15//! - and a small set of entry points that wire generated actor code.
16//!
17//! Low-level execution, storage, and engine internals live in
18//! `icydb-core` and are re-exposed selectively through stable facade modules.
19//!
20//! ## Crate layout
21//!
22//! - `build` *(host builds)*
23//!   Host-side build-script facade for generated actor glue. Downstream
24//!   canister `build.rs` files should use this module rather than depending on
25//!   lower-level implementation crates directly.
26//!
27//! - `model`
28//!   Application-model declarations, macros, reusable types, validation, and
29//!   normalization. Runtime-enabled downstream crates should use this
30//!   re-export instead of adding a second IcyDB package dependency.
31//!
32//! - `traits` / `types` / `value`
33//!   Stable runtime building blocks used by generated code.
34//!
35//! - `metrics` *(internal)*
36//!   Runtime metrics internals exposed for generated administration endpoints.
37//!
38//! - `Error` / `ErrorKind` / `ErrorOrigin`
39//!   Shared error types for generated code and runtime boundaries.
40//!
41//! - `db`
42//!   The public database façade: sessions, SQL/dynamic reads, structural
43//!   mutations, and accepted-schema-bound typed adapters.
44//!
45//! Generated SQL endpoints are controller-gated by default. A declaration may
46//! instead install one synchronous application read guard; neither form is an
47//! anonymous public read endpoint template.
48//!
49//! The operational lane contract lives in
50//! `docs/contracts/READ_ADMISSION.md`.
51//! Endpoint migration recipes live in `docs/guides/read-intent.md`.
52//!
53//! ## Preludes
54//!
55//! - `prelude`
56//!   Opinionated runtime prelude for canister actor code.
57//!   Intended to be glob-imported in `lib.rs` to keep endpoints concise.
58//!
59//! ## Internal boundaries
60//!
61//! Generated code targets explicit facade surfaces (`traits`, `db`, and
62//! `__macro`) instead of a broad internal-export module.
63
64// Generated actor glue resolves this package through its canonical crate name.
65extern crate self as icydb;
66
67pub use icydb_core::types::{ParseU256Error, U256};
68/// Application-model authoring through the public IcyDB facade.
69///
70/// Runtime-enabled consumers should normally depend only on `icydb` and use
71/// `icydb::model::prelude`. Schema-only tooling may still depend directly on
72/// the independently published `icydb-model` package.
73pub use icydb_model as model;
74pub use icydb_model_macros::{request_execution, test};
75
76// core modules
77#[doc(hidden)]
78pub use icydb_core::types;
79
80pub mod value {
81    pub use icydb_core::value::{InputValue, OutputValue, PublicEnumValue, PublicValue, ValueTag};
82}
83
84#[doc(hidden)]
85#[cfg(feature = "metrics")]
86pub mod metrics {
87    pub use icydb_core::metrics::{
88        ConvergenceMetrics, EntityMetrics, InstructionMetrics, MetricsReport,
89        SchemaLifecycleMetrics, metrics_reset_all,
90    };
91
92    /// Read the current metrics window and exact bounded journal debt.
93    ///
94    /// Returns a typed error when the database's committed journal controls
95    /// cannot be observed. Query reads do not advance recovery or record work.
96    pub fn metrics_report() -> Result<MetricsReport, crate::Error> {
97        Ok(icydb_core::metrics::metrics_report()?)
98    }
99}
100
101// facade modules
102#[cfg(not(target_arch = "wasm32"))]
103pub mod build {
104    //! Host-side build-script facade for generated actor glue.
105    //!
106    //! This module is the advertised downstream build-script API. Add `icydb`
107    //! to `[build-dependencies]`, then call `icydb::build::build_canister!()`
108    //! from `build.rs`. Model-graph code generation is owned by `icydb-model`.
109    //! This module is host-only and is not part of Wasm runtime builds.
110
111    pub use crate::build_canister;
112
113    /// Emit one generated private actor module for a build script.
114    ///
115    /// This function is expansion support for [`build_canister!`].
116    ///
117    /// # Errors
118    ///
119    /// Returns an environment or filesystem error when Cargo does not provide
120    /// `OUT_DIR` or the generated actor cannot be written.
121    ///
122    /// # Panics
123    ///
124    /// Panics if model validation fails, the canonical path is not a canister,
125    /// or the consuming package's IcyDB dependency cannot be resolved.
126    #[doc(hidden)]
127    pub fn __emit_canister_for_build_script(
128        canister_path: &str,
129        build_script: &str,
130    ) -> Result<(), Box<dyn std::error::Error>> {
131        println!("cargo:rerun-if-changed={build_script}");
132        println!("cargo:rustc-check-cfg=cfg(feature, values(\"test-admin-api\"))");
133        let out_dir = std::env::var("OUT_DIR")?;
134        let actor_file = std::path::PathBuf::from(out_dir).join("actor.rs");
135        let actor = icydb_model::build::generate(canister_path);
136        std::fs::write(actor_file, actor)?;
137
138        Ok(())
139    }
140}
141pub mod db;
142pub mod guards;
143pub mod diagnostic {
144    //! Compact diagnostic identity for CLI and canister callers.
145
146    pub use icydb_diagnostic_code::{
147        Diagnostic, DiagnosticAggregateKind, DiagnosticBacklogResource, DiagnosticCode,
148        DiagnosticComponentKind, DiagnosticConstraintContext, DiagnosticConstraintKind,
149        DiagnosticDecodeReason, DiagnosticDetail, DiagnosticExecutionBudgetResource,
150        DiagnosticExecutionBudgetScope, DiagnosticExecutionLane, DiagnosticFactSchemaMismatch,
151        DiagnosticFactTag, DiagnosticFunctionKind, DiagnosticMutationOperation,
152        DiagnosticOperatorKind, DiagnosticTypeFamily, ErrorClass, ErrorCode, ErrorOrigin,
153        MAX_PUBLIC_DIAGNOSTIC_FACTS, MAX_PUBLIC_QUERY_FIELD_BYTES, QueryErrorKind, QueryFieldRole,
154        QueryFieldSchemaMismatch, QueryProjectionCode, QueryReadAdmissionCode, RuntimeBoundaryCode,
155        RuntimeErrorKind, SchemaDdlAdmissionCode, SchemaMigrationCode, SqlFeatureCode,
156        SqlLoweringCode, SqlSurfaceMismatchCode, SqlWriteBoundaryCode, pack_u32_pair,
157        unpack_u32_pair, validate_known_diagnostic_fact_schema, validate_query_field_schema,
158        validate_raw_diagnostic_fact_schema,
159    };
160}
161mod error;
162pub mod traits;
163pub use error::{
164    ConstraintValidationFindingOutput, ConstraintValuePath, ConstraintValuePathComponent,
165    DiagnosticFact, Error, ErrorKind, ErrorOrigin, QueryErrorKind, QueryFieldDiagnostic,
166    RuntimeErrorKind,
167};
168pub use guards::{
169    ReadAuthorizationContext, ReadAuthorizationDecision, ReadAuthorizationGuard,
170    ReadAuthorizationSurface,
171};
172/// Declare a host-owned stable-memory range before memory bootstrap.
173///
174/// Omitting `mode` selects `Reserved`. A Reserved range does not provide slots
175/// for fresh logical allocation requests. Explicitly select `Allowed` to grant
176/// fresh logical placements for the database's permanent namespace:
177///
178/// ```rust,ignore
179/// icydb::ic_memory_range!(
180///     authority = "icydb.app", start = 100, end = 254, mode = Allowed,
181/// );
182/// ```
183///
184/// This re-exports the upstream macro without changing its default or granting
185/// memory implicitly. An Allowed pool must also contain enough eligible free slots.
186pub use ic_memory::ic_memory_range;
187pub use icydb_diagnostic_code::ErrorCode;
188
189// Macro/runtime wiring surface used by generated code.
190// This is intentionally narrow and not semver-stable.
191#[doc(hidden)]
192pub mod __macro {
193    pub use crate::db::{
194        TypedEntityDescriptor, TypedFieldDescriptor, TypedFieldType, ensure_default_memory_manager,
195        execute_generated_storage_report,
196    };
197    pub use crate::guards::{authorize_schema_read, authorize_sql_read};
198    pub use ic_memory::{ic_memory_declaration, ic_memory_key};
199    #[cfg(feature = "sql")]
200    pub use icydb_core::db::sql_statement_dispatch;
201    pub use icydb_core::db::{
202        CompositePrimaryKeyValue, DataStore, DbSession as CoreDbSession, EntityKey, EntityKeyBytes,
203        EntityKeyBytesError, IndexStore, JournalTailStore, KeyValueCodec, PrimaryKeyDecode,
204        PrimaryKeyEncode, PrimaryKeyEncodeError, PrimaryKeyValue, SchemaStore,
205        StoreAllocationIdentities, StoreAllocationIdentity, StoreRegistry,
206        StoreRuntimeStorageCapabilities, validate_entity_key_bytes_buffer,
207    };
208    pub use icydb_core::error::{ErrorClass, ErrorOrigin, InternalError};
209    pub use icydb_core::traits::{CanisterKind, Path};
210    pub use icydb_core::value::Value;
211    pub use icydb_schema::{DEFAULT_BIG_INT_MAX_BYTES, ScalarType};
212}
213
214// Dependencies used by generated actor glue. Application-model macro
215// dependencies are owned separately by `icydb-model`.
216#[doc(hidden)]
217pub mod __reexports {
218    pub use candid;
219    pub use ic_cdk;
220    pub use ic_timers;
221}
222
223//
224// Actor Prelude
225// using _ brings traits into scope and avoids name conflicts
226//
227
228pub mod prelude {
229    pub use crate::db::{
230        query,
231        query::{
232            CollectionOperator, CompareOperator, FieldCompareOperator, FieldRef, FilterExpr,
233            FilterValue, JunctionOperator, OrderExpr, OrderTerm, SetOperator, StateOperator, asc,
234            count, count_by, desc, exists, field, first, last, max, max_by, min, min_by, sum,
235        },
236    };
237    pub use crate::{
238        db,
239        traits::{EntitySource as _, Inner as _, Path as _},
240        types::*,
241        value::{InputValue, OutputValue},
242    };
243    pub use candid::CandidType;
244    pub use serde::{Deserialize, Serialize};
245}
246
247//
248// -------------------------- CODE -----------------------------------
249//
250
251//
252// Consts
253//
254
255// Workspace version re-export for downstream tooling/tests.
256pub const VERSION: &str = env!("CARGO_PKG_VERSION");
257
258//
259// Macros
260//
261
262/// Generate one canister's private actor module from its authored schema type.
263///
264/// Resolves the type through its canonical model [`Path`](model::Path), so
265/// relative and imported type paths use the same registered identity.
266/// Track shared declaration files with Cargo `rerun-if-changed` directives.
267///
268/// # Panics
269///
270/// Panics if model validation fails, the type is not a registered canister,
271/// or the consuming package's IcyDB dependency cannot be resolved.
272#[cfg(not(target_arch = "wasm32"))]
273#[macro_export]
274macro_rules! build_canister {
275    ($canister_ty:ty) => {{
276        $crate::build::__emit_canister_for_build_script(
277            <$canister_ty as $crate::model::Path>::PATH,
278            file!(),
279        )
280    }};
281}
282
283/// Include the generated private actor module emitted by [`build_canister!`].
284///
285/// The zero-argument form owns hidden install and post-upgrade lifecycle
286/// entries for canisters without application hooks. Applications that own
287/// the complete lifecycle root use participant mode and invoke the matching
288/// hidden IcyDB participant synchronously:
289///
290/// ```ignore
291/// icydb::start!(participant);
292///
293/// #[ic_cdk::init]
294/// fn init() {
295///     crate::__icydb_lifecycle_participant::init();
296/// }
297///
298/// #[ic_cdk::post_upgrade]
299/// fn post_upgrade() {
300///     crate::__icydb_lifecycle_participant::post_upgrade();
301/// }
302/// ```
303///
304/// Applications that want IcyDB to own the lifecycle exports while running
305/// application callbacks afterward use the composed form:
306///
307/// ```ignore
308/// icydb::start! {
309///     init(args: InitArgs) => application::init;
310///     post_upgrade() => application::post_upgrade;
311/// }
312/// ```
313///
314/// IcyDB registers its startup watchdog before invoking either callback. The
315/// callback remains application-owned and must observe `startup_state()`
316/// before restoring timers, caches, or other database-dependent state.
317#[macro_export]
318macro_rules! start {
319    () => {
320        $crate::__icydb_start_actor!();
321        $crate::__icydb_start_lifecycle!();
322    };
323
324    (participant) => {
325        $crate::__icydb_start_actor!();
326        $crate::__icydb_start_participant_lifecycle!();
327    };
328
329    (
330        init($($init_arg:ident : $init_ty:ty),* $(,)?) => $init:path;
331        post_upgrade($($upgrade_arg:ident : $upgrade_ty:ty),* $(,)?) => $post_upgrade:path;
332    ) => {
333        $crate::__icydb_start_actor!();
334        $crate::__icydb_start_lifecycle! {
335            init($($init_arg: $init_ty),*) => $init;
336            post_upgrade($($upgrade_arg: $upgrade_ty),*) => $post_upgrade;
337        }
338    };
339}
340
341#[doc(hidden)]
342#[macro_export]
343#[expect(
344    clippy::crate_in_macro_def,
345    reason = "participant functions must call the consuming canister's generated actor"
346)]
347macro_rules! __icydb_start_participant_lifecycle {
348    () => {
349        #[doc(hidden)]
350        #[allow(
351            dead_code,
352            reason = "downstream canisters may select participant hooks through generated lifecycle wiring"
353        )]
354        pub(crate) mod __icydb_lifecycle_participant {
355            use std::cell::Cell;
356
357            #[derive(Clone, Copy)]
358            enum State {
359                Idle,
360                Running,
361                Completed,
362            }
363
364            std::thread_local! {
365                static STATE: Cell<State> = const { Cell::new(State::Idle) };
366            }
367
368            // Native traps unwind instead of rolling back a replicated IC
369            // message. Reset only that test/runtime model so retry exercises
370            // the same latch transition that message rollback provides on Wasm.
371            #[cfg(not(target_family = "wasm"))]
372            struct NativeRollbackGuard {
373                completed: bool,
374            }
375
376            #[cfg(not(target_family = "wasm"))]
377            impl NativeRollbackGuard {
378                const fn new() -> Self {
379                    Self { completed: false }
380                }
381
382                const fn complete(&mut self) {
383                    self.completed = true;
384                }
385            }
386
387            #[cfg(not(target_family = "wasm"))]
388            impl Drop for NativeRollbackGuard {
389                fn drop(&mut self) {
390                    if !self.completed {
391                        STATE.with(|state| state.set(State::Idle));
392                    }
393                }
394            }
395
396            // Both lifecycle phases deliberately converge here. Completed is
397            // shared across them so any duplicate returns before generated
398            // timer or recovery state can be observed or changed.
399            fn participate(work: fn() -> ()) {
400                let should_run = STATE.with(|state| match state.get() {
401                    State::Idle => {
402                        state.set(State::Running);
403                        true
404                    }
405                    State::Running => $crate::__reexports::ic_cdk::trap(
406                        "IcyDB lifecycle participant re-entered while running",
407                    ),
408                    State::Completed => false,
409                });
410
411                if !should_run {
412                    return;
413                }
414
415                #[cfg(not(target_family = "wasm"))]
416                let mut rollback = NativeRollbackGuard::new();
417
418                work();
419                STATE.with(|state| state.set(State::Completed));
420
421                #[cfg(not(target_family = "wasm"))]
422                rollback.complete();
423            }
424
425            /// Participate synchronously in the canister's init lifecycle.
426            #[doc(hidden)]
427            pub(crate) fn init() -> () {
428                let participate: fn() -> () = crate::__icydb_generated::__icydb_startup_init;
429                self::participate(participate);
430            }
431
432            /// Participate synchronously in the canister's post-upgrade lifecycle.
433            #[doc(hidden)]
434            pub(crate) fn post_upgrade() -> () {
435                let participate: fn() -> () =
436                    crate::__icydb_generated::__icydb_startup_post_upgrade;
437                self::participate(participate);
438            }
439        }
440    };
441}
442
443#[doc(hidden)]
444#[macro_export]
445#[expect(
446    clippy::crate_in_macro_def,
447    reason = "lifecycle wrappers must call the consuming canister's generated actor"
448)]
449macro_rules! __icydb_start_lifecycle {
450    () => {
451        #[$crate::__reexports::ic_cdk::init(hidden = true)]
452        fn __icydb_startup_init() {
453            crate::__icydb_generated::__icydb_startup_init();
454        }
455
456        #[$crate::__reexports::ic_cdk::post_upgrade(hidden = true)]
457        fn __icydb_startup_post_upgrade() {
458            crate::__icydb_generated::__icydb_startup_post_upgrade();
459        }
460    };
461
462    (
463        init($($init_arg:ident : $init_ty:ty),* $(,)?) => $init:path;
464        post_upgrade($($upgrade_arg:ident : $upgrade_ty:ty),* $(,)?) => $post_upgrade:path;
465    ) => {
466        #[$crate::__reexports::ic_cdk::init]
467        fn __icydb_startup_init($($init_arg: $init_ty),*) {
468            crate::__icydb_generated::__icydb_startup_init();
469            let (): () = $crate::db::with_request_execution(|| ($init)($($init_arg),*));
470        }
471
472        #[$crate::__reexports::ic_cdk::post_upgrade]
473        fn __icydb_startup_post_upgrade($($upgrade_arg: $upgrade_ty),*) {
474            crate::__icydb_generated::__icydb_startup_post_upgrade();
475            let (): () = $crate::db::with_request_execution(
476                || ($post_upgrade)($($upgrade_arg),*)
477            );
478        }
479    };
480}
481
482#[doc(hidden)]
483#[macro_export]
484#[expect(
485    clippy::crate_in_macro_def,
486    reason = "generated actor bindings must live in the consuming canister crate"
487)]
488macro_rules! __icydb_start_actor {
489    () => {
490        #[doc(hidden)]
491        struct __IcydbStartRootMarker;
492
493        #[doc(hidden)]
494        const fn __icydb_start_root_binding(_: __IcydbStartRootMarker) {}
495
496        const _: fn(__IcydbStartRootMarker) = crate::__icydb_start_root_binding;
497
498        #[allow(
499            dead_code,
500            reason = "generated actor members are feature-dependent in downstream canisters"
501        )]
502        mod __icydb_generated {
503            #[doc(hidden)]
504            pub(crate) const __ICYDB_START_BINDING: () = ();
505
506            include!(concat!(env!("OUT_DIR"), "/actor.rs"));
507        }
508
509        #[allow(
510            unused_imports,
511            reason = "minimal downstream canisters may use only a subset of generated actor conveniences"
512        )]
513        use __icydb_generated::{db, db_with_request_root, startup_state};
514    };
515}
516
517#[doc(hidden)]
518#[cfg(feature = "sql")]
519#[macro_export]
520macro_rules! __icydb_with_sql_items {
521    ($($item:item)*) => { $($item)* };
522}
523
524#[doc(hidden)]
525#[cfg(feature = "metrics")]
526#[macro_export]
527macro_rules! __icydb_with_metrics_items {
528    ($($item:item)*) => { $($item)* };
529}
530
531#[doc(hidden)]
532#[cfg(not(feature = "metrics"))]
533#[macro_export]
534macro_rules! __icydb_with_metrics_items {
535    ($($item:item)*) => {};
536}
537
538#[doc(hidden)]
539#[cfg(not(feature = "sql"))]
540#[macro_export]
541macro_rules! __icydb_with_sql_items {
542    ($($item:item)*) => {};
543}
544
545#[doc(hidden)]
546#[cfg(feature = "migration")]
547#[macro_export]
548macro_rules! __icydb_with_migration_items {
549    ($($item:item)*) => { $($item)* };
550}
551
552#[doc(hidden)]
553#[cfg(not(feature = "migration"))]
554#[macro_export]
555macro_rules! __icydb_with_migration_items {
556    ($($item:item)*) => {};
557}
558
559#[doc(hidden)]
560#[cfg(feature = "sql")]
561#[macro_export]
562macro_rules! __icydb_with_sql_endpoint {
563    ($endpoint:literal; $($item:item)*) => { $($item)* };
564}
565
566#[doc(hidden)]
567#[cfg(feature = "metrics")]
568#[macro_export]
569macro_rules! __icydb_with_metrics_endpoint {
570    ($endpoint:literal; $($item:item)*) => { $($item)* };
571}
572
573#[doc(hidden)]
574#[cfg(not(feature = "metrics"))]
575#[macro_export]
576macro_rules! __icydb_with_metrics_endpoint {
577    ($endpoint:literal; $($item:item)*) => {
578        compile_error!(concat!(
579            "endpoint declaration `",
580            $endpoint,
581            "` requires the `icydb/metrics` Cargo feature"
582        ));
583    };
584}
585
586#[doc(hidden)]
587#[cfg(feature = "migration")]
588#[macro_export]
589macro_rules! __icydb_with_migration_endpoint {
590    ($endpoint:literal; $($item:item)*) => { $($item)* };
591}
592
593#[doc(hidden)]
594#[cfg(not(feature = "migration"))]
595#[macro_export]
596macro_rules! __icydb_with_migration_endpoint {
597    ($endpoint:literal; $($item:item)*) => {
598        compile_error!(concat!(
599            "endpoint declaration `",
600            $endpoint,
601            "` requires the `icydb/migration` Cargo feature"
602        ));
603    };
604}
605
606#[doc(hidden)]
607#[cfg(not(feature = "sql"))]
608#[macro_export]
609macro_rules! __icydb_with_sql_endpoint {
610    ($endpoint:literal; $($item:item)*) => {
611        compile_error!(concat!(
612            "endpoint declaration `",
613            $endpoint,
614            "` requires the `icydb/sql` Cargo feature"
615        ));
616    };
617}
618
619#[doc(hidden)]
620#[cfg(feature = "migration")]
621#[macro_export]
622macro_rules! __icydb_require_migration_capability {
623    () => {};
624}
625
626#[doc(hidden)]
627#[cfg(not(feature = "migration"))]
628#[macro_export]
629macro_rules! __icydb_require_migration_capability {
630    () => {
631        compile_error!("source migration declarations require the `icydb/migration` Cargo feature");
632    };
633}
634
635/// Declare the complete fixed IcyDB endpoint surface exported by this canister.
636#[macro_export]
637#[expect(
638    clippy::crate_in_macro_def,
639    reason = "endpoints! must prove crate-root placement in the consuming canister"
640)]
641macro_rules! endpoints {
642    ($($declaration:tt)*) => {
643        #[doc(hidden)]
644        struct __IcydbEndpointsRootMarker;
645
646        #[doc(hidden)]
647        const fn __icydb_endpoints_root_binding(_: __IcydbEndpointsRootMarker) {}
648
649        const _: fn(__IcydbEndpointsRootMarker) = crate::__icydb_endpoints_root_binding;
650
651        #[doc(hidden)]
652        #[allow(
653            unused_imports,
654            reason = "declared endpoint families determine whether the generated facade alias is referenced"
655        )]
656        use $crate as __icydb_facade;
657
658        #[used]
659        static __ICYDB_ENDPOINT_DECLARATIONS: () =
660            crate::__icydb_generated::__ICYDB_START_BINDING;
661
662        $crate::__icydb_endpoints_internal!($($declaration)*);
663    };
664}
665
666#[doc(hidden)]
667#[macro_export]
668#[expect(
669    clippy::crate_in_macro_def,
670    reason = "endpoint wrappers call the consuming canister's private generated module"
671)]
672macro_rules! __icydb_endpoints_internal {
673    () => {};
674
675    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(
676        introspection = false,
677        authorization = guard($guard:path) $(,)?
678    ); $($rest:tt)*) => {
679        $(#[cfg($($cfg)*)])*
680        #[used]
681        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
682        $(#[cfg($($cfg)*)])*
683        $crate::__icydb_with_sql_endpoint! {
684            "icydb_sql_query";
685            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
686            fn __icydb_export_icydb_query(
687                sql: String,
688            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
689                let guard: $crate::ReadAuthorizationGuard = $guard;
690                $crate::__macro::authorize_sql_read(
691                    $crate::__reexports::ic_cdk::api::msg_caller(),
692                    guard,
693                )?;
694                $crate::db::with_request_execution(|| {
695                    crate::__icydb_generated::endpoint_handlers::sql_query::<false>(sql)
696                })
697            }
698        }
699        $crate::__icydb_endpoints_internal!($($rest)*);
700    };
701
702    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(
703        introspection = true,
704        authorization = guard($guard:path) $(,)?
705    ); $($rest:tt)*) => {
706        $(#[cfg($($cfg)*)])*
707        #[used]
708        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
709        $(#[cfg($($cfg)*)])*
710        $crate::__icydb_with_sql_endpoint! {
711            "icydb_sql_query";
712            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
713            fn __icydb_export_icydb_query(
714                sql: String,
715            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
716                let guard: $crate::ReadAuthorizationGuard = $guard;
717                $crate::__macro::authorize_sql_read(
718                    $crate::__reexports::ic_cdk::api::msg_caller(),
719                    guard,
720                )?;
721                $crate::db::with_request_execution(|| {
722                    crate::__icydb_generated::endpoint_handlers::sql_query::<true>(sql)
723                })
724            }
725        }
726        $crate::__icydb_endpoints_internal!($($rest)*);
727    };
728
729    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(introspection = false); $($rest:tt)*) => {
730        $(#[cfg($($cfg)*)])*
731        #[used]
732        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
733        $(#[cfg($($cfg)*)])*
734        $crate::__icydb_with_sql_endpoint! {
735            "icydb_sql_query";
736            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
737            fn __icydb_export_icydb_query(
738                sql: String,
739            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
740                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
741                $crate::db::with_request_execution(|| {
742                    crate::__icydb_generated::endpoint_handlers::sql_query::<false>(sql)
743                })
744            }
745        }
746        $crate::__icydb_endpoints_internal!($($rest)*);
747    };
748
749    ($(#[cfg($($cfg:tt)*)])* icydb_sql_query(introspection = true); $($rest:tt)*) => {
750        $(#[cfg($($cfg)*)])*
751        #[used]
752        static __ICYDB_ENDPOINT_DECLARATION_QUERY: () = ();
753        $(#[cfg($($cfg)*)])*
754        $crate::__icydb_with_sql_endpoint! {
755            "icydb_sql_query";
756            #[$crate::__reexports::ic_cdk::query(name = "icydb_query")]
757            fn __icydb_export_icydb_query(
758                sql: String,
759            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
760                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
761                $crate::db::with_request_execution(|| {
762                    crate::__icydb_generated::endpoint_handlers::sql_query::<true>(sql)
763                })
764            }
765        }
766        $crate::__icydb_endpoints_internal!($($rest)*);
767    };
768
769    ($(#[cfg($($cfg:tt)*)])* icydb_ddl; $($rest:tt)*) => {
770        $(#[cfg($($cfg)*)])*
771        #[used]
772        static __ICYDB_ENDPOINT_DECLARATION_DDL: () = ();
773        $(#[cfg($($cfg)*)])*
774        $crate::__icydb_with_sql_endpoint! {
775            "icydb_ddl";
776            #[$crate::__reexports::ic_cdk::update(name = "icydb_ddl")]
777            fn __icydb_export_icydb_ddl(
778                sql: String,
779            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
780                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
781                $crate::db::with_request_execution(|| {
782                    crate::__icydb_generated::endpoint_handlers::sql_ddl(sql)
783                })
784            }
785        }
786        $crate::__icydb_endpoints_internal!($($rest)*);
787    };
788
789    ($(#[cfg($($cfg:tt)*)])* icydb_update(admission = primary_key_only); $($rest:tt)*) => {
790        $(#[cfg($($cfg)*)])*
791        #[used]
792        static __ICYDB_ENDPOINT_DECLARATION_UPDATE: () = ();
793        $(#[cfg($($cfg)*)])*
794        $crate::__icydb_with_sql_endpoint! {
795            "icydb_update";
796            #[$crate::__reexports::ic_cdk::update(name = "icydb_update")]
797            fn __icydb_export_icydb_update(
798                sql: String,
799            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
800                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
801                $crate::db::with_request_execution(|| {
802                    crate::__icydb_generated::endpoint_handlers::sql_update_primary_key(sql)
803                })
804            }
805        }
806        $crate::__icydb_endpoints_internal!($($rest)*);
807    };
808
809    ($(#[cfg($($cfg:tt)*)])* icydb_update(admission = bounded_deterministic); $($rest:tt)*) => {
810        $(#[cfg($($cfg)*)])*
811        #[used]
812        static __ICYDB_ENDPOINT_DECLARATION_UPDATE: () = ();
813        $(#[cfg($($cfg)*)])*
814        $crate::__icydb_with_sql_endpoint! {
815            "icydb_update";
816            #[$crate::__reexports::ic_cdk::update(name = "icydb_update")]
817            fn __icydb_export_icydb_update(
818                sql: String,
819            ) -> Result<__icydb_facade::db::sql::SqlQueryResult, __icydb_facade::Error> {
820                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
821                $crate::db::with_request_execution(|| {
822                    crate::__icydb_generated::endpoint_handlers::sql_update_bounded(sql)
823                })
824            }
825        }
826        $crate::__icydb_endpoints_internal!($($rest)*);
827    };
828
829    ($(#[cfg($($cfg:tt)*)])* icydb_integrity; $($rest:tt)*) => {
830        $(#[cfg($($cfg)*)])*
831        #[used]
832        static __ICYDB_ENDPOINT_DECLARATION_INTEGRITY: () = ();
833        $(#[cfg($($cfg)*)])*
834        $crate::__icydb_with_sql_endpoint! {
835            "icydb_integrity";
836            #[allow(
837                clippy::result_large_err,
838                reason = "generated integrity endpoints preserve the public typed error contract"
839            )]
840            #[$crate::__reexports::ic_cdk::update(name = "icydb_integrity")]
841            fn __icydb_export_icydb_integrity(
842                sql: String,
843            ) -> Result<__icydb_facade::db::IntegrityCheckResult, __icydb_facade::db::SqlIntegrityError> {
844                crate::__icydb_generated::endpoint_authorization::require_sql_controller()
845                    .map_err(__icydb_facade::db::SqlIntegrityError::Sql)?;
846                $crate::db::with_request_execution(|| {
847                    crate::__icydb_generated::endpoint_handlers::sql_integrity(sql)
848                })
849            }
850        }
851        $crate::__icydb_endpoints_internal!($($rest)*);
852    };
853
854    ($(#[cfg($($cfg:tt)*)])* icydb_fixtures_reset; $($rest:tt)*) => {
855        $(#[cfg($($cfg)*)])*
856        #[used]
857        static __ICYDB_ENDPOINT_DECLARATION_FIXTURES_RESET: () = ();
858        $(#[cfg($($cfg)*)])*
859        #[cfg(not(feature = "test-admin-api"))]
860        compile_error!("endpoint declaration `icydb_fixtures_reset` requires the canister `test-admin-api` Cargo feature");
861        $(#[cfg($($cfg)*)])*
862        #[cfg(feature = "test-admin-api")]
863        $crate::__icydb_with_sql_endpoint! {
864            "icydb_fixtures_reset";
865            #[$crate::__reexports::ic_cdk::update(name = "icydb_fixtures_reset")]
866            fn __icydb_export_icydb_fixtures_reset() -> Result<(), __icydb_facade::Error> {
867                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
868                $crate::db::with_request_execution(|| {
869                    crate::__icydb_generated::endpoint_handlers::fixtures_reset()
870                })
871            }
872        }
873        $crate::__icydb_endpoints_internal!($($rest)*);
874    };
875
876    ($(#[cfg($($cfg:tt)*)])* icydb_fixtures_load(handler = $handler:path); $($rest:tt)*) => {
877        $(#[cfg($($cfg)*)])*
878        #[used]
879        static __ICYDB_ENDPOINT_DECLARATION_FIXTURES_LOAD: () = ();
880        $(#[cfg($($cfg)*)])*
881        #[cfg(not(feature = "test-admin-api"))]
882        compile_error!("endpoint declaration `icydb_fixtures_load` requires the canister `test-admin-api` Cargo feature");
883        $(#[cfg($($cfg)*)])*
884        #[cfg(feature = "test-admin-api")]
885        $crate::__icydb_with_sql_endpoint! {
886            "icydb_fixtures_load";
887            #[$crate::__reexports::ic_cdk::update(name = "icydb_fixtures_load")]
888            fn __icydb_export_icydb_fixtures_load() -> Result<(), __icydb_facade::Error> {
889                crate::__icydb_generated::endpoint_authorization::require_sql_controller()?;
890                let handler: fn() -> Result<(), $crate::Error> = $handler;
891                $crate::db::with_request_execution(|| {
892                    crate::__icydb_generated::endpoint_handlers::fixtures_load(handler)
893                })
894            }
895        }
896        $crate::__icydb_endpoints_internal!($($rest)*);
897    };
898
899    ($(#[cfg($($cfg:tt)*)])* icydb_metrics(authorization = public); $($rest:tt)*) => {
900        $(#[cfg($($cfg)*)])*
901        #[used]
902        static __ICYDB_ENDPOINT_DECLARATION_METRICS: () = ();
903        $(#[cfg($($cfg)*)])*
904        $crate::__icydb_with_metrics_endpoint! {
905            "icydb_metrics";
906            #[$crate::__reexports::ic_cdk::query(name = "icydb_metrics")]
907            fn __icydb_export_icydb_metrics(
908            ) -> Result<__icydb_facade::metrics::MetricsReport, __icydb_facade::Error> {
909                crate::__icydb_generated::endpoint_handlers::metrics()
910            }
911        }
912        $crate::__icydb_endpoints_internal!($($rest)*);
913    };
914
915    ($(#[cfg($($cfg:tt)*)])* icydb_metrics(authorization = controller); $($rest:tt)*) => {
916        $(#[cfg($($cfg)*)])*
917        #[used]
918        static __ICYDB_ENDPOINT_DECLARATION_METRICS: () = ();
919        $(#[cfg($($cfg)*)])*
920        $crate::__icydb_with_metrics_endpoint! {
921            "icydb_metrics";
922            #[$crate::__reexports::ic_cdk::query(name = "icydb_metrics")]
923            fn __icydb_export_icydb_metrics(
924            ) -> Result<__icydb_facade::metrics::MetricsReport, __icydb_facade::Error> {
925                crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
926                crate::__icydb_generated::endpoint_handlers::metrics()
927            }
928        }
929        $crate::__icydb_endpoints_internal!($($rest)*);
930    };
931
932    ($(#[cfg($($cfg:tt)*)])* icydb_metrics_reset; $($rest:tt)*) => {
933        $(#[cfg($($cfg)*)])*
934        #[used]
935        static __ICYDB_ENDPOINT_DECLARATION_METRICS_RESET: () = ();
936        $(#[cfg($($cfg)*)])*
937        $crate::__icydb_with_metrics_endpoint! {
938            "icydb_metrics_reset";
939            #[$crate::__reexports::ic_cdk::update(name = "icydb_metrics_reset")]
940            fn __icydb_export_icydb_metrics_reset() -> Result<(), __icydb_facade::Error> {
941                crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
942                crate::__icydb_generated::endpoint_handlers::metrics_reset()
943            }
944        }
945        $crate::__icydb_endpoints_internal!($($rest)*);
946    };
947
948    ($(#[cfg($($cfg:tt)*)])* icydb_snapshot; $($rest:tt)*) => {
949        $(#[cfg($($cfg)*)])*
950        #[used]
951        static __ICYDB_ENDPOINT_DECLARATION_SNAPSHOT: () = ();
952        $(#[cfg($($cfg)*)])*
953        #[$crate::__reexports::ic_cdk::query(name = "icydb_snapshot")]
954        fn __icydb_export_icydb_snapshot() -> Result<__icydb_facade::db::StorageReport, __icydb_facade::Error> {
955            crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
956            $crate::db::with_request_execution(|| {
957                crate::__icydb_generated::endpoint_handlers::snapshot()
958            })
959        }
960        $crate::__icydb_endpoints_internal!($($rest)*);
961    };
962
963    ($(#[cfg($($cfg:tt)*)])* icydb_schema(authorization = public); $($rest:tt)*) => {
964        $(#[cfg($($cfg)*)])*
965        #[used]
966        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA: () = ();
967        $(#[cfg($($cfg)*)])*
968        #[$crate::__reexports::ic_cdk::query(name = "icydb_schema")]
969        fn __icydb_export_icydb_schema(
970        ) -> Result<Vec<__icydb_facade::db::EntitySchemaDescription>, __icydb_facade::Error> {
971            $crate::db::with_request_execution(|| {
972                crate::__icydb_generated::endpoint_handlers::schema()
973            })
974        }
975        $crate::__icydb_endpoints_internal!($($rest)*);
976    };
977
978    ($(#[cfg($($cfg:tt)*)])* icydb_schema(
979        authorization = guard($guard:path) $(,)?
980    ); $($rest:tt)*) => {
981        $(#[cfg($($cfg)*)])*
982        #[used]
983        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA: () = ();
984        $(#[cfg($($cfg)*)])*
985        #[$crate::__reexports::ic_cdk::query(name = "icydb_schema")]
986        fn __icydb_export_icydb_schema(
987        ) -> Result<Vec<__icydb_facade::db::EntitySchemaDescription>, __icydb_facade::Error> {
988            let guard: $crate::ReadAuthorizationGuard = $guard;
989            $crate::__macro::authorize_schema_read(
990                $crate::__reexports::ic_cdk::api::msg_caller(),
991                guard,
992            )?;
993            $crate::db::with_request_execution(|| {
994                crate::__icydb_generated::endpoint_handlers::schema()
995            })
996        }
997        $crate::__icydb_endpoints_internal!($($rest)*);
998    };
999
1000    ($(#[cfg($($cfg:tt)*)])* icydb_schema(authorization = controller); $($rest:tt)*) => {
1001        $(#[cfg($($cfg)*)])*
1002        #[used]
1003        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA: () = ();
1004        $(#[cfg($($cfg)*)])*
1005        #[$crate::__reexports::ic_cdk::query(name = "icydb_schema")]
1006        fn __icydb_export_icydb_schema(
1007        ) -> Result<Vec<__icydb_facade::db::EntitySchemaDescription>, __icydb_facade::Error> {
1008            crate::__icydb_generated::endpoint_authorization::require_schema_controller()?;
1009            $crate::db::with_request_execution(|| {
1010                crate::__icydb_generated::endpoint_handlers::schema()
1011            })
1012        }
1013        $crate::__icydb_endpoints_internal!($($rest)*);
1014    };
1015
1016    ($(#[cfg($($cfg:tt)*)])* icydb_schema_migrate; $($rest:tt)*) => {
1017        $(#[cfg($($cfg)*)])*
1018        #[used]
1019        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA_MIGRATE: () = ();
1020        $(#[cfg($($cfg)*)])*
1021        $crate::__icydb_with_migration_endpoint! {
1022            "icydb_schema_migrate";
1023            #[$crate::__reexports::ic_cdk::update(name = "icydb_schema_migrate")]
1024            fn __icydb_export_icydb_schema_migrate(
1025                command: __icydb_facade::db::SchemaMigrationCommand,
1026            ) -> Result<__icydb_facade::db::SchemaMigrationStatusPage, __icydb_facade::Error> {
1027                crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
1028                $crate::db::with_request_execution(|| {
1029                    crate::__icydb_generated::endpoint_handlers::schema_migrate(command)
1030                })
1031            }
1032        }
1033        $crate::__icydb_endpoints_internal!($($rest)*);
1034    };
1035
1036    ($(#[cfg($($cfg:tt)*)])* icydb_schema_migration; $($rest:tt)*) => {
1037        $(#[cfg($($cfg)*)])*
1038        #[used]
1039        static __ICYDB_ENDPOINT_DECLARATION_SCHEMA_MIGRATION: () = ();
1040        $(#[cfg($($cfg)*)])*
1041        $crate::__icydb_with_migration_endpoint! {
1042            "icydb_schema_migration";
1043            #[$crate::__reexports::ic_cdk::query(name = "icydb_schema_migration")]
1044            fn __icydb_export_icydb_schema_migration(
1045                request: __icydb_facade::db::SchemaMigrationStatusRequest,
1046            ) -> Result<__icydb_facade::db::SchemaMigrationStatusPage, __icydb_facade::Error> {
1047                crate::__icydb_generated::endpoint_authorization::require_operational_controller()?;
1048                $crate::db::with_request_execution(|| {
1049                    crate::__icydb_generated::endpoint_handlers::schema_migration(&request)
1050                })
1051            }
1052        }
1053        $crate::__icydb_endpoints_internal!($($rest)*);
1054    };
1055
1056    ($(#[cfg($($cfg:tt)*)])* $endpoint:ident $($rest:tt)*) => {
1057        compile_error!(concat!("unknown or invalid IcyDB endpoint declaration `", stringify!($endpoint), "`"));
1058    };
1059
1060    (#[$attribute:meta] $($rest:tt)*) => {
1061        compile_error!("IcyDB endpoint declarations accept only `#[cfg(...)]` attributes");
1062    };
1063
1064    ($($invalid:tt)+) => {
1065        compile_error!("invalid IcyDB endpoint declaration syntax");
1066    };
1067}
1068
1069/// Access the active request's database session.
1070///
1071/// Use `db!()` in ordinary generated endpoints and nested helpers. Every call
1072/// shares the execution counters installed at request entry. Manual IC-CDK,
1073/// framework, and timer entries establish that boundary with
1074/// [`request_execution`]; lifecycle callbacks declared by the composed
1075/// [`start!`] form receive it automatically.
1076///
1077/// `db!(&request_root)` is the explicit low-level integration form for a
1078/// framework that already owns a request root. Obtain that root from
1079/// [`db::with_request_execution_root`](crate::db::with_request_execution_root);
1080/// passing it never creates fresh counters and fails if a different root is
1081/// already active.
1082#[macro_export]
1083#[expect(clippy::crate_in_macro_def)]
1084macro_rules! db {
1085    () => {
1086        crate::db()
1087    };
1088    ($request_root:expr) => {
1089        crate::db_with_request_root($request_root)
1090    };
1091}
1092
1093//
1094// Helpers
1095//
1096
1097#[cfg(all(test, not(target_arch = "wasm32")))]
1098mod tests {
1099    use crate::build;
1100
1101    struct ModelWrapper(u64);
1102
1103    impl icydb_model::Inner<u64> for ModelWrapper {
1104        fn inner(&self) -> &u64 {
1105            &self.0
1106        }
1107
1108        fn into_inner(self) -> u64 {
1109            self.0
1110        }
1111    }
1112
1113    impl icydb_model::Path for ModelWrapper {
1114        const PATH: &'static str = "icydb::tests::ModelWrapper";
1115    }
1116
1117    #[test]
1118    fn build_facade_exports_typed_entrypoint() {
1119        fn assert_model_inner<T: crate::traits::Inner<u64>>() {}
1120
1121        assert_model_inner::<ModelWrapper>();
1122        let wrapper = ModelWrapper(7);
1123        assert_eq!(*crate::traits::Inner::inner(&wrapper), 7);
1124        assert_eq!(crate::traits::Inner::into_inner(wrapper), 7);
1125
1126        std::hint::black_box(
1127            build_facade_macros_resolve as fn() -> Result<(), Box<dyn std::error::Error>>,
1128        );
1129    }
1130
1131    fn build_facade_macros_resolve() -> Result<(), Box<dyn std::error::Error>> {
1132        build::build_canister!(ModelWrapper)?;
1133
1134        Ok(())
1135    }
1136}