linkspace/
lib.rs

1// Copyright Anton Sol
2// This Source Code Form is subject to the terms of the Mozilla Public
3// License, v. 2.0. If a copy of the MPL was not distributed with this
4// file, You can obtain one at https://mozilla.org/MPL/2.0/.
5#![deny(missing_debug_implementations)]
6#![allow(clippy::type_complexity)]
7#![feature(
8    array_chunks,
9    slice_from_ptr_range,
10    non_null_from_ref,
11    split_array,
12    cell_update,
13    str_split_remainder,
14    try_blocks,
15    extend_one,
16    slice_split_once,
17    iterator_try_collect,
18    thread_local,
19    write_all_vectored,
20    type_alias_impl_trait,
21    concat_bytes,
22    try_trait_v2
23)]
24#![doc = include_str!("../../../README.md")]
25#![doc = include_str!("../README.md")]
26
27/// The only error format linkspace supports - alias for anyhow::Error
28pub type LkError = anyhow::Error;
29/// Result for [LkError]
30pub type LkResult<T = ()> = std::result::Result<T, LkError>;
31
32/// Re-export common types
33pub mod prelude {
34    pub use super::*;
35    pub use argument_traits::{LkEnv, Stmnts, TryAsSpace};
36    pub use linkspace_core::{
37        ShouldBreak,
38        byte_fmt::{
39            AB, B64,
40            endian_types::{self, U64},
41        },
42        point::{
43            Domain, Error as PointError, GroupID, Link, LkHash, LkIdentity, LkPath, LkPathArray,
44            LkPathError, LkPathPart, LkPathSegm, MutXHeader, NewPoint, PFields, PFieldsExt, Point,
45            PointArc, PointBox, PointExt, PointParts, PointPtr, PointTypeFlags, PubKey,
46            RecvPointPtr, SetXHeader, SigningExt, Stamp, Tag, WithNewXHeader, XFlags, XHeader, ab,
47            abf, as_abtxt_c,
48            link::{link, linkf},
49            lkpath, now,
50            repr::PointFmt,
51            try_ab,
52        },
53        predicate::exprs::{CoreTestOp, TestOp},
54        query::options::KnownOptions,
55        space_expr::Space,
56    };
57    pub use std::ops::ControlFlow;
58}
59
60use linkspace_core::point as lkp;
61use prelude::*;
62
63pub use prelude::LkIdentity;
64
65pub use point::{lk_datapoint, lk_keypoint, lk_linkpoint};
66/// creating points
67pub mod point {
68    use std::io;
69
70    use anyhow::Context;
71    use linkspace_core::{ShouldBreak, space_expr::Space};
72    pub use lkp::CheckHash;
73    use lkp::{PartialPoint, reroute::MutXHeaderPoint};
74
75    use super::*;
76    /**
77
78    create a datapoint with upto MAX_CONTENT_SIZE bytes
79
80    ```
81    # use linkspace::{*,prelude::*,abe::*};
82    # fn main() -> LkResult{
83    let datap = lk_datapoint(b"Some data")?;
84    assert_eq!(datap.hash().to_string(), "ATE6XG70_mx-kV2GCZUIkNijcPa8gd1-C3OYu1pXqcU");
85    assert_eq!(datap.data() , b"Some data");
86    # Ok(())}
87    ```
88
89    **/
90    pub fn lk_datapoint(data: &[u8]) -> LkResult<PointBox> {
91        lk_datapoint_ref(data).map(|v| v.as_box())
92    }
93    /// like [lk_datapoint] but keeps it on the stack in rust enum format.
94    pub fn lk_datapoint_ref(data: &[u8]) -> LkResult<PointParts<'_>> {
95        Ok(lkp::try_datapoint_ref(data)?)
96    }
97    /**
98
99    create a new linkpoint [PointBox]
100    ```
101    # use linkspace::{*,prelude::{*,endian_types::*},abe::*};
102    # fn main() -> LkResult{
103
104    let datap = lk_datapoint(b"this is a datapoint")?;
105    let path = lkpath(&[b"hello",b"world"]);
106    let links = [
107        Link{tag: ab(b"a datapoint"),ptr:datap.hash()},
108        Link{tag: ab(b"another tag"),ptr:PUBLIC}
109    ];
110    let data = b"extra data for the linkpoint";
111    let stamp = Some(U64::new(0)); // None == Some(now()).
112    let linkpoint = lk_linkpoint(data,b"mydomain",&links)?;
113
114    assert_eq!(linkpoint.hash().to_string(),"IdnnQjgxJLGxLZGKdaXWVxc82-U8KyJoyKK3sKlD8Lc");
115    assert_eq!(linkpoint.data(), data);
116    assert_eq!(*linkpoint.get_group(), PUBLIC);
117
118    # Ok(())}
119
120    ```
121    **/
122    pub fn lk_linkpoint(space: &dyn TryAsSpace, data: &[u8], links: &[Link]) -> LkResult<PointBox> {
123        let Space {
124            domain,
125            group,
126            path,
127        } = space.try_as_space(&())?;
128        lk_linkpoint_ref(domain, group, &path, data, links, lkp::now()).map(|v| v.as_box())
129    }
130    /// like [lk_linkpoint] but keeps it on the stack in rust enum format.
131    pub fn lk_linkpoint_ref<'o>(
132        domain: Domain,
133        group: GroupID,
134        path: &'o LkPath,
135        data: &'o [u8],
136        links: &'o [Link],
137        stamp: Stamp,
138    ) -> LkResult<PointParts<'o>> {
139        Ok(lkp::try_linkpoint_ref(
140            group, domain, path, links, data, stamp,
141        )?)
142    }
143    /// create a keypoint, i.e. a signed [lk_linkpoint]
144    pub fn lk_keypoint(space: &dyn TryAsSpace, data: &[u8], links: &[Link]) -> LkResult<PointBox> {
145        let Space {
146            domain,
147            group,
148            path,
149        } = space.try_as_space(&())?;
150        linkspace_core::thread_local::with_id(|key| {
151            lk_keypoint_ref(key, domain, group, &path, data, links, lkp::now()).map(|v| v.as_box())
152        })
153        .context("No signing key has been set")?
154    }
155    /// like [lk_keypoint] but keeps it on the stack in rust enum format.
156    pub fn lk_keypoint_ref<'o>(
157        key: &LkIdentity,
158        domain: Domain,
159        group: GroupID,
160        path: &'o LkPath,
161        data: &'o [u8],
162        links: &'o [Link],
163        stamp: Stamp,
164    ) -> LkResult<PointParts<'o>> {
165        Ok(lkp::try_keypoint_ref(
166            group, domain, path, links, data, stamp, key,
167        )?)
168    }
169    /// utility function to build either [lk_keypoint] or [lk_linkpoint] depending on the optional key argument
170    pub fn lk_point_ref<'o>(
171        key: Option<&LkIdentity>,
172        domain: Domain,
173        group: GroupID,
174        path: &'o LkPath,
175        data: &'o [u8],
176        links: &'o [Link],
177        stamp: Stamp,
178    ) -> LkResult<PointParts<'o>> {
179        Ok(lkp::try_point(
180            group, domain, path, links, data, stamp, key,
181        )?)
182    }
183
184    pub fn lk_deserialize_ref<'a>(
185        check: CheckHash,
186        mut buf: &'a [u8],
187        cb: &mut dyn FnMut(&PointPtr) -> ShouldBreak,
188    ) -> Result<&'a [u8], PointError> {
189        while !buf.is_empty() {
190            let (p, rest) = lkp::deserialize::read_point(buf, check)?;
191            buf = rest;
192            if cb(&p) {
193                break;
194            };
195        }
196        Ok(buf)
197    }
198    pub fn lk_deserialize(buf: &[u8], mode: IOMode) -> Result<(PointBox, &[u8]), PointError> {
199        let (mut head, inner, tail) = PartialPoint::take(buf, mode.check())?;
200        mode.set_header(&mut head.xheader);
201        let point = unsafe {
202            PointBox::read_excl2(head, CheckHash::Check, |out| out.copy_from_slice(inner))?
203        };
204        mode.check_private(&point)?;
205        Ok((point, tail))
206    }
207    pub fn lk_deserialize_arc(buf: &[u8], mode: IOMode) -> Result<(PointArc, &[u8]), PointError> {
208        let (mut head, inner, tail) = PartialPoint::take(buf, mode.check())?;
209        mode.set_header(&mut head.xheader);
210        let point = unsafe {
211            PointArc::read_excl(head, CheckHash::Check, |out| out.copy_from_slice(inner))?
212        };
213        mode.check_private(&point)?;
214        Ok((point, tail))
215    }
216
217    /// Determine the length required for lk_deserialize to succeed - expects at least [consts::MIN_POINT_LEN] bytes
218    pub fn lk_deserialize_len(buf: &[u8]) -> Result<u16, PointError> {
219        lkp::deserialize::read_point_len(buf)
220    }
221
222    #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
223    #[cfg_attr(feature = "pyo3", pyo3::pyclass(frozen, eq))]
224    #[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
225    /// Signals the source of a point.
226    pub enum IOMode {
227        #[default]
228        Transmit,
229        /// A trusted origin.
230        /// Sets the XFlags::STORAGE bit, skips validating hashes, does not increment hop
231        Storage,
232    }
233    impl IOMode {
234        pub fn check_private(self, p: &impl Point) -> Result<(), PointError> {
235            if self != IOMode::Storage {
236                p.check_private()?
237            }
238            Ok(())
239        }
240        pub fn check(self) -> CheckHash {
241            match self {
242                IOMode::Transmit => CheckHash::Check,
243                IOMode::Storage => CheckHash::SkipHash,
244            }
245        }
246        pub fn set_header(self, h: &mut XHeader) {
247            match self {
248                IOMode::Transmit => {
249                    h.incr_xhop();
250                    h.xflags.remove(XFlags::STORAGE);
251                }
252                IOMode::Storage => {
253                    h.xflags.insert(XFlags::STORAGE);
254                }
255            }
256        }
257    }
258
259    /// Writes any form of [Point] into the binary point format
260    /// When reading you need to indicate what xfields you're accepting with mask [XHeader::KEEP_COMMON] and friends
261    /// Mode does additional checks - specifically in IOMode::Transmit it rejects PRIVATE group, increments hop, and clears XFlags::Storage.
262    pub fn lk_serialize_ref(
263        p: &dyn Point,
264        mode: IOMode,
265        out: &mut dyn FnMut(lkp::ByteSegments) -> LkResult<()>,
266    ) -> LkResult<()> {
267        let mut point = MutXHeaderPoint::new(p);
268        match mode {
269            IOMode::Transmit => {
270                p.check_private().map_err(io::Error::other)?;
271                point.xheader.incr_xhop();
272                point.xheader.xflags.remove(XFlags::STORAGE);
273            }
274            IOMode::Storage => {
275                point.xheader.xflags.insert(XFlags::STORAGE);
276            }
277        }
278        out(point.byte_segments())
279    }
280    pub fn lk_serialize(p: &dyn Point) -> Box<[u8]> {
281        let mut v = vec![].into_boxed_slice();
282        lk_serialize_ref(p, IOMode::Transmit, &mut |o| {
283            v = o.to_bytes();
284            Ok(())
285        })
286        .unwrap();
287        v
288    }
289    pub fn as_netarc(p: &dyn Point) -> PointArc {
290        p.as_arc()
291    }
292    pub fn as_netbox(p: &dyn Point) -> PointBox {
293        p.as_box()
294    }
295}
296
297pub use query::{LkQuery, lkq_add, lkq_insert_mut, lkq_new, lkq_space};
298/// [LkQuery] functions
299pub mod query {
300    /**
301    A set of predicates and options used to select a range of packets
302
303    The supported predicate fields are [PredicateType].
304    The known options are [KnownOptions].
305
306    ```
307    # use linkspace::{*,prelude::*,abe::*, query::*};
308    # fn main() -> LkResult{
309
310
311    // Add multiple predicates and options at once.
312    let query_str = r#"
313      group = [#:pub]
314      domain = [a:hello]
315      prefix = /some/path
316    "#;
317    let mut query = lkq_new(&query_str,&())?;
318
319    // the domain:group:prefix is so common it has an allias [lkq_space]
320
321    // As these are AB-Expressions they are evaluated in the [UserScope] context
322    lkq_add_mut(&mut query,"prefix = /some/[0]",&[b"path" as &[u8]])?;
323
324    {
325       // add the predicate that the stamp should hold a value less 100
326       let mut query_with_stamp = lkq_add(&query, &"stamp < [u64:100]",&())?;
327       // conflicting predicates are not allowed
328       let result = lkq_add_mut(&mut query_with_stamp, &"stamp > [u64:100]",&());
329       assert!( result.is_err());
330    }
331
332    // When adding only a single predicate you might be able to avoid an encoding step
333    lkq_insert_mut(&mut query,"stamp","<", &*now())?;
334    // Predicates get merged if they overlap
335    lkq_insert_mut(&mut query,"stamp","<",&lka_eval("[now:-1D]",&())?)?;
336
337    println!("{}",&lkq_stringify(&query,false));
338
339
340    # Ok(()) }
341    ```
342
343    Unlike predicates, the order in which options are can matter.
344    */
345    #[derive(Clone, PartialEq, Default)]
346    #[repr(transparent)]
347    #[cfg_attr(feature = "pyo3", pyo3::pyclass(unsendable, eq))]
348    #[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
349    pub struct LkQuery(pub(crate) linkspace_core::query::Query);
350
351    /// An immutable empty_query
352    pub static LK_Q: LkQuery = LkQuery(linkspace_core::query::Query::DEFAULT);
353
354    impl std::fmt::Display for LkQuery {
355        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
356            self.0.fmt(f, true)
357        }
358    }
359    impl std::fmt::Debug for LkQuery {
360        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
361            self.0.fmt(f, false)
362        }
363    }
364
365    use anyhow::Context;
366    pub use linkspace_core::predicate::predicate_type::PredicateType;
367    pub use linkspace_core::prelude::KnownOptions;
368    use linkspace_core::{prelude::ExtPredicate, query::Query};
369
370    use super::*;
371    /// Create a new [Query] from a set of statements. See [Query]
372    pub fn lkq_new(stmnts: &dyn Stmnts, scope: &dyn LkEnv) -> LkResult<LkQuery> {
373        let mut q = LK_Q.clone();
374        lkq_add_mut(&mut q, stmnts, scope)?;
375        Ok(q)
376    }
377
378    /// Create a new [LkQuery] specifically for matching a hash. Sets the right mode and limit count. See [system::lks_scan_hashes] if you don't care to watch the db.
379    pub fn lkq_hash(hash: LkHash, stmnts: &dyn Stmnts) -> LkResult<LkQuery> {
380        let mut q = LkQuery(linkspace_core::query::Query::hash_eq(hash));
381        lkq_add_mut(&mut q, stmnts, &())?;
382        if let Some(wopts) = &mut q.0.watch_options {
383            wopts.id.extend(&now().0);
384        }
385        Ok(q)
386    }
387    /** Create a query form a space_expr i.e. domain:group:/path */
388    /** The domain and group resolve to [crate::thread_local] variants if not supplied. i.e. "::/path" */
389    /** The path matches the exact path.*/
390    /** alternatively use "::/my/path//*"  for all matches, or you can be more specific such as "::/my/path//././?" for matches starting with /my/path and 4 or 5 parts. */ */
391    pub fn lkq_space(
392        space_expr: &str,
393        stmnts: &dyn Stmnts,
394        scope: &dyn LkEnv,
395    ) -> LkResult<LkQuery> {
396        let qexpr: linkspace_core::space_expr::ShortQueryExpr = space_expr.parse()?;
397        let scope = scope.as_scope();
398        let mut q = LkQuery(qexpr.eval(&scope.as_dyn())?);
399        lkq_add_mut(&mut q, stmnts, &scope)?;
400        Ok(q)
401    }
402
403    pub fn lkq_watch(mut query: LkQuery, watch_prefix: &[u8]) -> LkQuery {
404        let wo = query.0.watch_options.get_or_insert(Default::default());
405        wo.id = [watch_prefix, &wo.id].concat();
406        query
407    }
408    pub fn lkq_path(space: &Space, pubkey: Option<PubKey>) -> LkQuery {
409        let q = Query::subspace(
410            Some(space.domain),
411            Some(space.group),
412            &space.path,
413            true,
414            pubkey,
415        );
416        LkQuery(q)
417    }
418    pub fn lkq_prefix(space: &Space, pubkey: Option<PubKey>) -> LkQuery {
419        let q = Query::subspace(
420            Some(space.domain),
421            Some(space.group),
422            &space.path,
423            false,
424            pubkey,
425        );
426        LkQuery(q)
427    }
428
429    /// Add a statement or option similar to [lkq_add_mut], but use three arguments and skip an evaluation step.
430    /// I.e. lkq_add_mut(q,"{field} {op} {lka_encode(bytes)}") becomes lkq_insert_mut(q,field,op,bytes).
431    ///
432    /// Accepts options in the form ```lkq_insert_mut(q,"","get",b"log-asc")```.
433    pub fn lkq_insert_mut(query: &mut LkQuery, field: &str, test: &str, val: &[u8]) -> LkResult {
434        if field.is_empty() {
435            query.0.add_option(test, &[val])
436        } else {
437            let epre = ExtPredicate {
438                kind: field
439                    .parse()
440                    .with_context(|| format!("No such Field '{field}'"))?,
441                op: test
442                    .parse()
443                    .map_err(|e| anyhow::anyhow!("bad operator `{e}`"))?,
444                val: val.to_vec().into(),
445            };
446            query.0.predicates.add_ext_predicate(epre)
447        }
448    }
449    /// Merge statements into a new query.
450    pub fn lkq_add(query: &LkQuery, stmnts: &dyn Stmnts, scope: &dyn LkEnv) -> LkResult<LkQuery> {
451        let mut query = query.clone();
452        lkq_add_mut(&mut query, stmnts, scope)?;
453        Ok(query)
454    }
455
456    /// Add multiple ABE encoded statements to a [Query]
457    pub fn lkq_add_mut(query: &mut LkQuery, stmnts: &dyn Stmnts, scope: &dyn LkEnv) -> LkResult {
458        let scope = scope.as_scope();
459        let scope = scope.as_dyn();
460        stmnts.per_line(&mut |line| query.0.parse(line, scope))
461    }
462    /// Clear a [Query] for reuse
463    pub fn lkq_clear_mut(query: &mut LkQuery) {
464        *query = LK_Q.clone();
465    }
466    /// Get the string representation of a [Query] - abe indicates wether to use \[\] expressions
467    pub fn lkq_stringify(query: &LkQuery, abe: bool) -> String {
468        query.0.to_str(abe)
469    }
470    /// If the query has any domain predicate return it - errors if it is a set of domains
471    pub fn lkq_exact_domain(query: &LkQuery) -> LkResult<Option<Domain>> {
472        let domain_set = query.0.predicates.domain;
473        if domain_set == Default::default() {
474            return Ok(None);
475        }
476        domain_set
477            .as_eq()
478            .map(Into::into)
479            .context("query contains a set of domains")
480            .map(Some)
481    }
482    /// If the query has any group predicate return it - errors if it is a set of groups
483    pub fn lkq_exact_group(query: &LkQuery) -> LkResult<Option<GroupID>> {
484        let group_set = query.0.predicates.group;
485        if group_set == Default::default() {
486            return Ok(None);
487        }
488        group_set
489            .as_eq()
490            .map(Into::into)
491            .context("query contains a set of groups")
492            .map(Some)
493    }
494
495    pub use linkspace_core::query::CompiledQuery;
496    /// Compile a [Query] into a function that tests packets to deteremine if they match
497    #[allow(clippy::type_complexity)]
498    pub fn lkq_compile(q: LkQuery) -> LkResult<CompiledQuery> {
499        q.0.compile()
500    }
501}
502
503/// cryptographic key functions for use in [lk_keypoint]
504pub mod identity {
505    use super::prelude::*;
506    pub use linkspace_commons_internal::argon2_identity::{
507        self, Costs, DEFAULT_COST, INSECURE_COST, PARANOID_COST,
508    };
509
510    /// generate a new key
511    pub fn lki_generate() -> LkIdentity {
512        LkIdentity::generate()
513    }
514    /// Encrypt an identity using argon2d with the public key as the salt in URL_SAFE base64 (replaces "+/" with "-_")
515    /// Empty passwords are always encrypted with the cheapest preset
516    pub fn lki_encrypt(id: &LkIdentity, pass: &[u8]) -> String {
517        argon2_identity::encrypt(
518            id,
519            pass,
520            if pass.is_empty() {
521                INSECURE_COST
522            } else {
523                DEFAULT_COST
524            },
525        )
526    }
527    /// NOTE: password length & complexity determines your security
528    pub fn lki_encrypt_paranoid(id: &LkIdentity, pass: &[u8]) -> String {
529        argon2_identity::encrypt(id, pass, PARANOID_COST)
530    }
531    /// decrypt the result of [lki_encrypt]
532    pub fn lki_decrypt(enckey: &str, pass: &[u8]) -> LkResult<LkIdentity> {
533        Ok(linkspace_commons_internal::argon2_identity::decrypt(
534            enckey, pass,
535        )?)
536    }
537    /// read the public key portion of a [lki_encrypt] string
538    pub fn lki_pubkey(encrypted_identity: &str) -> LkResult<PubKey> {
539        Ok(linkspace_commons_internal::argon2_identity::pubkey(
540            encrypted_identity,
541        )?)
542    }
543    /// Seed randomness to generate an adhoc ID usable without syncing.
544    pub fn lki_adhoc(space: &dyn TryAsSpace, pass: &[u8]) -> LkResult<LkIdentity> {
545        let space = space.try_as_space(&())?;
546        Ok(linkspace_commons_internal::argon2_identity::adhoc(
547            space.domain,
548            space.group,
549            pass,
550        ))
551    }
552    #[doc(hidden)]
553    pub use linkspace_commons_internal::argon2_identity::test_key;
554}
555
556#[cfg(feature = "lmdb")]
557pub use system::lks_open;
558#[cfg(feature = "inmem")]
559pub use system::lks_open_inmem;
560#[cfg(all(any(feature = "lmdb", feature = "inmem"), not(target_family = "wasm")))]
561pub use system::lks_process_while;
562#[cfg(any(feature = "lmdb", feature = "inmem"))]
563pub use system::{
564    LkSystem,
565    cb::{on_point, on_point_then, try_on_point},
566    lks_process, lks_save, lks_scan, lks_tap,
567};
568
569#[cfg(any(feature = "lmdb", feature = "inmem"))]
570/// a system to watch for new points from other processes or threads
571pub mod system {
572    /**
573    A linkspace system.
574    The struct is not !Send. use [lks_open] in each thread.
575    The first opened in each thread is set as the thread 'default'.
576
577    It stores callbacks, a transaction to a storage engine, and receives/sends (inter-process) events of new writes.
578    open/create with [lks_open] or [lks_open_inmem].
579    Save packets with [lks_save]_*.
580    Get packets with [lks_scan]_*, [lks_watch], and [lks_tap].
581    Use [lks_process]_* to trigger watch and tap callbacks and to update the transaction.
582    NOTE: Saving a packet will not show up in a scan before calling one of the [lks_process]* functions.
583
584    To use multiple instances in a thread see [lks_chsys]
585    **/
586
587    #[derive(Clone, PartialEq)]
588    #[repr(transparent)]
589    #[cfg_attr(feature = "pyo3", pyo3::pyclass(unsendable, eq))]
590    #[cfg_attr(feature = "wasm", wasm_bindgen::prelude::wasm_bindgen)]
591    pub struct LkSystem(pub(crate) System);
592
593    use std::cell::Cell;
594
595    use crate::interop::sys_interop::System;
596    use anyhow::Context;
597    use linkspace_system::*;
598
599    impl std::fmt::Debug for LkSystem {
600        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
601            f.debug_struct("LkSystem")
602                .field("info", &lks_info(Some(self)))
603                .finish()
604        }
605    }
606
607    use crate::query::LkQuery;
608    use cb::PointHandler;
609    use linkspace_core::{prelude::PredicateType, saturating_cast};
610    use linkspace_system::thread_local::*;
611    use tracing::debug_span;
612
613    use super::*;
614    /// open or create a [LkSystem].
615    ///
616    /// will look at `path` | $LKDB | '$HOME/lkdb'
617    /// In the unlikely case you wish to open multiple storage engines, note that the first call (per thread) sets the instance used for [lka_eval] (see [varscope] for more info).
618    #[cfg(feature = "lmdb")]
619    pub fn lks_open(dir: impl AsRef<std::path::Path>) -> std::io::Result<LkSystem> {
620        let rt = linkspace_system::open_linkspace_dir(dir.as_ref(), true)?;
621        Ok(LkSystem(rt))
622    }
623    /// opens an in memory linkspace system.
624    /// Unlike [lks_open] the data is not persistent, nor can it be shared across processes, but it can be [lks_split] and used across threads.
625    #[cfg(feature = "inmem")]
626    pub fn lks_open_inmem(name: &str) -> LkSystem {
627        let rt = linkspace_system::open_inmem(name);
628        LkSystem(rt)
629    }
630    #[cfg(feature = "inmem")]
631    #[doc(hidden)]
632    pub use linkspace_system::storage_engine::inmem::IN_MEMORY_DIRTY_XSTAMP;
633
634    pub fn lks_from_info(lksi: &LksInfo) -> LkResult<LkSystem> {
635        match lksi.kind.as_str() {
636            #[cfg(feature = "lmdb")]
637            "lmdb" => Ok(lks_open(&lksi.name)?),
638            #[cfg(feature = "inmem")]
639            "inmem" => Ok(lks_open_inmem(&lksi.name)),
640            kind => anyhow::bail!("Not compiled with support for {kind} system"),
641        }
642    }
643    /// create a clone without any callbacks. Usually to send to another thread. Requires its own lks_process calls.
644    pub fn lks_split() -> LkSystem {
645        LkSystem(get_system().unwrap().new_system())
646    }
647
648    /// get the current system - defaults to the first call to [lks_open*]
649    pub fn lks_current() -> Option<crate::LkSystem> {
650        linkspace_system::thread_local::opt_get_system().map(crate::LkSystem)
651    }
652
653    pub fn lks_chsys<A>(lks: &LkSystem, fnc: impl FnOnce() -> A) -> A {
654        use_sys(&lks.0, fnc)
655    }
656
657    /// Last recv stamp of this system. i.e. last result from [lks_process]
658    pub fn lks_last() -> Stamp {
659        get_system().unwrap().last()
660    }
661
662    /// save a point - must [lks_process]_* for it to be show up in a [lks_scan]_*. Returns true if new and false if it is old.
663    /// By convention if the current xstamp of a point equals zero, the database will copy the recv stamp into xstamp - This does not update the point provided as an argument.
664    pub fn lks_save(point: &dyn Point) -> std::io::Result<bool> {
665        save_dyn_one(get_system_io()?.storage_engine(), point).map(|o| o.is_written())
666    }
667    /// save multiple packets at once - returns the number of new packets written
668    pub fn lks_save_all(ppoint: &[&dyn Point]) -> std::io::Result<usize> {
669        let (start, excl) = lks_save_all_ext(ppoint)?;
670        Ok((excl.get() - start.get()) as usize)
671    }
672    /// returns the range [incusive,exclusive) of recv stamps used to save new packets. total_new = r.1-r.0
673    pub fn lks_save_all_ext(ppoint: &[&dyn Point]) -> std::io::Result<(Stamp, Stamp)> {
674        let range =
675            with_system(|lks| save_dyn_iter(lks.storage_engine(), ppoint.iter().copied(), |_| ()))?;
676        Ok((range.start.into(), range.end.into()))
677    }
678    /// Calls on_new - after saving - for each new point. [Point::recv] will return the stamp as just set in the database.
679    pub fn lks_save_new(
680        ppoint: &[&dyn Point],
681        on_new: &mut dyn FnMut(&dyn Point),
682    ) -> std::io::Result<usize> {
683        let range = with_system(|lks| {
684            save_dyn_iter(lks.storage_engine(), ppoint.iter().copied(), |p| on_new(&p))
685        })?;
686        Ok(range.count())
687    }
688    /// Call the callback for every query matched packet in storage.
689    /// Note that new packets are only readable after a call to [lks_process]_*.
690    /// Unlike [lks_tap] this only checks the :scan / :linkscan options (it does nothing for :notify, or :watch)
691    /// If no :scan is set assumes :scan space-desc
692    /// Break early if the callback returns true.
693    /// returns number of matches. Positive if cb returned true, otherwise negative
694    pub fn lks_scan(
695        query: &LkQuery,
696        cb: &mut dyn for<'o> FnMut(&'o dyn Point) -> ShouldBreak,
697    ) -> LkResult<i64> {
698        let mut match_counter = 0; // excludes linkscan
699        let options = query.0.get_scan_options();
700        with_system(|lks| {
701            let breaks = lks.scan_query(
702                options,
703                &query.0,
704                &mut match_counter,
705                None,
706                &mut |point: &dyn Point, _wid: Option<&[u8]>, _: &System| {
707                    cb::should_break(cb(point))
708                },
709            )?;
710            Ok(linkspace_system::system::return_value(
711                match_counter,
712                breaks,
713            ))
714        })
715    }
716
717    /** read a single packet directly without copying.**/
718    pub fn lks_scan_one_ref<A>(
719        query: &LkQuery,
720        cb: &mut dyn FnMut(&dyn Point) -> A,
721    ) -> LkResult<Option<A>> {
722        let mut result = None;
723        let mut calls = |p: &dyn Point| {
724            result = Some(cb(p));
725            true
726        };
727        with_system(|lks| {
728            let mode = query.0.get_scan_options();
729            let reader = lks.dyn_reader();
730            let _ = dyn_query(&*reader, 1, mode, &query.0.predicates, None, &mut calls);
731        });
732        Ok(result)
733    }
734    /** read a single packet directly without copying.**/
735    pub fn lks_scan_one_hash_ref<A>(
736        hash: LkHash,
737        cb: &mut dyn FnMut(&dyn Point) -> A,
738    ) -> LkResult<Option<A>> {
739        with_system(|lks| {
740            let mut result = None;
741            lks.dyn_reader()
742                .get_points_by_hash(&mut [hash].into_iter(), &mut |point| {
743                    if let Ok(point) = point {
744                        result = Some(cb(point));
745                    }
746                    true
747                });
748            Ok(result)
749        })
750    }
751
752    /// TODO: DOC
753    pub fn lks_scan_hashes<H: AsRef<LkHash>>(
754        hashes: &mut dyn Iterator<Item = H>,
755        cb: &mut dyn FnMut(Option<&dyn Point>, H) -> ShouldBreak,
756    ) -> LkResult<i64> {
757        let mut c = 0;
758        with_system(|lks| {
759            let r = lks.dyn_reader();
760            let last_h: Cell<Option<H>> = Cell::new(None);
761            let mut iter = std::iter::from_fn(|| {
762                let item = hashes.next()?;
763                let hash = *item.as_ref();
764                last_h.set(Some(item));
765                Some(hash)
766            });
767            let breaks = r.get_points_by_hash(&mut iter, &mut |result| {
768                if result.is_ok() {
769                    c += 1;
770                }
771                cb(result.ok(), last_h.take().unwrap())
772            });
773            Ok(saturating_cast(c, !breaks))
774        })
775    }
776
777    /// [lks_tap] that ignores the :scan option, and inserts a :watch option if none are present.
778    pub fn lks_watch(query: LkQuery, cb: impl PointHandler + 'static) -> LkResult<()> {
779        lks_watch2(query, cb, debug_span!("lks_watch - (untraced)"))
780    }
781
782    /// [lks_watch] with a custom log [tracing::Span]
783    /// The span will be entered on every callback.
784    pub fn lks_watch2(
785        mut query: LkQuery,
786        cb: impl PointHandler + 'static,
787        span: tracing::Span,
788    ) -> LkResult<()> {
789        with_system(|lks| {
790            query.0.scan_options = None;
791            query.0.watch_options.get_or_insert_default();
792            lks.tap_query(query.0, interop::sys_interop::Handler(cb), span)?;
793            Ok(())
794        })
795    }
796
797    /**
798    If the :scan option is set, cb for all matching packets.
799    If the :watch option is set, watch for new matching packets.
800
801    The absolute return value is the number of times the callback was called during :scan phase.
802    A positive value means the callback finished by returning true|break - and won't register watch for further matches.
803
804    A 0 or negative value means it is watching for new packets
805    During [[lks_process]] or [[lks_process_while]] the cb is called for each matching packet.
806
807    The watch is finished when
808    - the cb returns true or [ControlFlow::Break].
809    - the predicate set will never match again (e.g. the 'i_*' or 'recv' predicate shall never match again )
810    - [[lks_stop]] is called with the matching watch id
811
812    **/
813    pub fn lks_tap(query: LkQuery, cb: impl PointHandler + 'static) -> LkResult<i64> {
814        lks_tap2(query, cb, debug_span!("lks_tap - (untraced)"))
815    }
816
817    /// [lks_tap] with a custom log [tracing::Span]
818    /// The span will be entered on every callback.
819    pub fn lks_tap2(
820        query: LkQuery,
821        cb: impl PointHandler + 'static,
822        span: tracing::Span,
823    ) -> LkResult<i64> {
824        with_system(|lks| lks.tap_query(query.0, interop::sys_interop::Handler(cb), span))
825    }
826
827    pub fn lks_multi_tap(
828        queries: Vec<LkQuery>,
829        mut cb: impl PointHandler + 'static,
830        any_close: bool,
831        span: tracing::Span,
832    ) -> LkResult<i64> {
833        with_system(|lks| {
834            if queries.is_empty() {
835                cb.on_close(&query::LK_Q, handlers::StopReason::Finish, 0, 0);
836                return Ok(0);
837            }
838            let mq = handlers::MultiQueryHandler::new(
839                interop::sys_interop::Handler(cb),
840                queries.len(),
841                any_close,
842            );
843            let mut total = 0;
844            let mut has_neg = false;
845            for q in queries {
846                let span = tracing::debug_span!(parent: &span,"multi query", inner=?q);
847                let r = lks.tap_query(q.0, mq.clone(), span)?;
848                has_neg = has_neg || r < 0;
849                total += r.abs();
850            }
851
852            Ok(total * if has_neg { -1 } else { 1 })
853        })
854    }
855
856    /// See [lks_tap2]
857    pub fn vspan(name: &str) -> tracing::Span {
858        tracing::debug_span!("{}", name)
859    }
860
861    pub fn lks_forget_hashes(
862        hashes: &[LkHash],
863        cb: &mut dyn FnMut(&dyn Point),
864    ) -> anyhow::Result<()> {
865        lks_mutate(MutateOp::Forget, hashes, cb)
866    }
867    pub fn lks_forget(query: &LkQuery, cb: &mut dyn FnMut(&dyn Point)) -> LkResult<()> {
868        let ro = query.0.get_scan_options();
869        with_system(|lks| {
870            let reader = lks.get_reader();
871            // TODO - reader should lock _after_ write lock
872            let mut hashes = get_query(&reader, ro, &query.0.predicates).map(|p| {
873                tracing::info!(point=%PointFmt(&p), "Removing");
874                p.hash()
875            });
876            storage_engine::methods::remove_by_hash(
877                lks.storage_engine(),
878                MutateOp::Forget,
879                &mut hashes,
880                cb,
881            )?;
882            Ok(())
883        })
884    }
885    pub use linkspace_system::storage_engine::tables_traits::MutateOp;
886    pub fn lks_mutate(
887        op: MutateOp,
888        hashes: &[LkHash],
889        cb: &mut dyn FnMut(&dyn Point),
890    ) -> anyhow::Result<()> {
891        with_system(|lks| {
892            Ok(storage_engine::methods::remove_by_hash(
893                lks.storage_engine(),
894                op,
895                &mut hashes.iter().copied(),
896                cb,
897            )?)
898        })
899    }
900    /// close lks_tap watches based on the query id ':watch ID' in the query.
901    pub fn lks_stop(id: &[u8], range: bool) {
902        with_system(|lks| {
903            if range {
904                lks.close_prefix(id)
905            } else {
906                lks.close(id)
907            }
908        })
909    }
910    pub fn lks_stop_query(query: &LkQuery, range: bool) -> LkResult<()> {
911        let id = query
912            .0
913            .watch_id()
914            .context("this query doesnt have a :watch ID")?;
915        with_system(|lks| {
916            if range {
917                lks.close_prefix(id)
918            } else {
919                lks.close(id)
920            }
921        });
922        Ok(())
923    }
924    /// process the log of new packets and trigger callbacks. Updates the reader to the latest state.
925    pub fn lks_process() -> Stamp {
926        with_system(|lks| lks.process())
927    }
928    /**
929    process callbacks until either:
930
931    The timeout has elapsed (0)
932    if A watch id is given:
933      => The watch was matched and the cb called: Positive if finished, negative if still active
934    if No watch id is given:
935      => # of new points processed
936    **/
937    #[cfg(not(target_family = "wasm"))]
938    pub fn lks_process_while(
939        watch: Option<&linkspace_core::query::WatchIDRef>,
940        timeout: Stamp,
941    ) -> LkResult<isize> {
942        let timeout = (timeout != Stamp::ZERO)
943            .then(|| std::time::Instant::now() + std::time::Duration::from_micros(timeout.get()));
944        _lks_process_while(watch, timeout).map(|i| i.as_isize())
945    }
946
947    #[doc(hidden)]
948    pub use linkspace_system::system::ProcessWhileResult;
949    #[doc(hidden)]
950    #[cfg(not(target_family = "wasm"))]
951    // simplifies python ffi bindings
952    pub fn _lks_process_while(
953        watch: Option<&linkspace_core::query::WatchIDRef>,
954        timeout: Option<std::time::Instant>,
955    ) -> LkResult<ProcessWhileResult> {
956        with_system(|lks| lks.run_while(timeout, watch))
957    }
958
959    /**
960    A future awaiting new [lks_save] to the storage engine.
961    **/
962    pub async fn lks_await_write() -> Stamp {
963        let lks = get_system().unwrap();
964        let result = lks.await_write().await;
965        unsafe { set_system(lks) };
966        result
967    }
968    /** an alias for `while(true) {lks_process(lks); lks_await_write(lks).await }`
969    Useful in an async context where other routines tap the system.
970     **/
971    pub async fn lks_process_async() {
972        let lks = get_system().unwrap();
973        loop {
974            lks.process();
975            lks.await_write().await;
976        }
977    }
978    /// iterate over all active (WatchID,Query, test result) - test result does not check for limits and recv bounds
979    pub fn lks_list_watches(
980        cb: &mut dyn FnMut(&[u8], &LkQuery, Result<(), PredicateType>),
981        point: Option<&dyn Point>,
982    ) {
983        with_system(|lks| {
984            for el in lks.dbg_watches().entries() {
985                let mut is_match = Ok(());
986                if let Some(p) = point {
987                    is_match = el.info.tests.test_point(p);
988                }
989                cb(
990                    &el.info.query_id,
991                    LkQuery::from_impl(&el.info.query),
992                    is_match,
993                )
994            }
995        })
996    }
997
998    #[cfg_attr(feature = "pyo3", pyo3::pyclass(frozen, get_all))]
999    #[cfg_attr(
1000        feature = "wasm",
1001        wasm_bindgen::prelude::wasm_bindgen(getter_with_clone, inspectable)
1002    )]
1003    #[derive(Debug, Clone)]
1004    /// miscellaneous information about the system
1005    pub struct LksInfo {
1006        /// the kind of system
1007        pub kind: String,
1008        /// if applicable - a path or name for the specific instance
1009        pub name: String,
1010        pub instance: usize,
1011    }
1012    impl std::fmt::Display for LksInfo {
1013        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1014            std::fmt::Debug::fmt(self, f)
1015        }
1016    }
1017    /// get [LkInfo] of a linkspace system
1018    pub fn lks_info(lks: Option<&LkSystem>) -> LkResult<LksInfo> {
1019        let lks = lks
1020            .map(|o| o.0.clone())
1021            .unwrap_or_else(|| get_system().unwrap());
1022        let instance = lks.instance();
1023        let se = lks.storage_engine();
1024        Ok(LksInfo {
1025            instance,
1026            // required utf8 name to use in LkEnvDataSync
1027            name: se
1028                .dir()
1029                .to_str()
1030                .context("TODO: non-utf8 name not supported ATM")?
1031                .to_string(),
1032            kind: match se {
1033                #[cfg(feature = "lmdb")]
1034                linkspace_system::storage_engine::impl_enum::StorageEngineImplEnum::Lmdb(_) => {
1035                    "lmdb".to_string()
1036                }
1037                #[cfg(feature = "inmem")]
1038                linkspace_system::storage_engine::impl_enum::StorageEngineImplEnum::InMem(_) => {
1039                    "inmem".to_string()
1040                }
1041            },
1042        })
1043    }
1044
1045    pub fn _lks_dump() {
1046        let lks = get_system().unwrap();
1047        let se = lks.storage_engine();
1048        storage_engine::impl_enum::debug_se(se);
1049    }
1050
1051    #[cfg(any(feature = "lmdb", feature = "inmem"))]
1052    /** [lks_tap] takes a [PointHandler].
1053     **/
1054    pub mod cb {
1055        pub use linkspace_system::handlers::StopReason;
1056
1057        use std::ops::{ControlFlow, Try};
1058
1059        use linkspace_core::{ShouldBreak, point::Point, query::WatchIDRef};
1060
1061        use super::query::LkQuery;
1062
1063        pub fn should_break(o: ShouldBreak) -> ControlFlow<()> {
1064            if o {
1065                ControlFlow::Break(())
1066            } else {
1067                ControlFlow::Continue(())
1068            }
1069        }
1070
1071        /// Callbacks stored in a [LkSystem] instance.
1072        /// Is implemented for `|p:&dyn Point, watch:Option<&WatchIDRef>, lks: &LkSystem| -> ControlFlow<()> {}`
1073        pub trait PointHandler {
1074            /// Handles matching points.
1075            fn on_point(
1076                &mut self,
1077                point: &dyn Point,
1078                watch: Option<&WatchIDRef>,
1079            ) -> ControlFlow<()>;
1080            /// Called when break, finished, or replaced
1081            fn on_close(
1082                &mut self,
1083                _: &LkQuery,
1084                _: StopReason,
1085                _total_calls: u64,
1086                _watch_calls: u64,
1087            ) {
1088            }
1089        }
1090        impl PointHandler for Box<dyn PointHandler> {
1091            fn on_point(
1092                &mut self,
1093                point: &dyn Point,
1094                watch: Option<&WatchIDRef>,
1095            ) -> ControlFlow<()> {
1096                (**self).on_point(point, watch)
1097            }
1098
1099            fn on_close(
1100                &mut self,
1101                query: &LkQuery,
1102                reason: StopReason,
1103                total: u64,
1104                watch_matches: u64,
1105            ) {
1106                (**self).on_close(query, reason, total, watch_matches)
1107            }
1108        }
1109        impl<T: PointHandler> PointHandler for std::rc::Rc<std::cell::RefCell<T>> {
1110            fn on_point(
1111                &mut self,
1112                point: &dyn Point,
1113                watch: Option<&WatchIDRef>,
1114            ) -> ControlFlow<()> {
1115                self.borrow_mut().on_point(point, watch)
1116            }
1117
1118            fn on_close(
1119                &mut self,
1120                q: &LkQuery,
1121                stop_reason: StopReason,
1122                total_calls: u64,
1123                watch_calls: u64,
1124            ) {
1125                self.borrow_mut()
1126                    .on_close(q, stop_reason, total_calls, watch_calls);
1127            }
1128        }
1129
1130        #[derive(Copy, Clone, Debug)]
1131        pub struct Handle<
1132            H: FnMut(&dyn Point, Option<&WatchIDRef>) -> ControlFlow<()>,
1133            S: FnMut(&LkQuery, StopReason, u64, u64),
1134        > {
1135            pub on_point: H,
1136            pub on_close: S,
1137        }
1138        impl<H, S> PointHandler for Handle<H, S>
1139        where
1140            H: FnMut(&dyn Point, Option<&WatchIDRef>) -> ControlFlow<()>,
1141            S: FnMut(&LkQuery, StopReason, u64, u64),
1142        {
1143            fn on_point(
1144                &mut self,
1145                point: &dyn Point,
1146                watch: Option<&WatchIDRef>,
1147            ) -> ControlFlow<()> {
1148                let h: &mut H = &mut self.on_point;
1149                h(point, watch)
1150            }
1151
1152            fn on_close(
1153                &mut self,
1154                query: &LkQuery,
1155                reason: StopReason,
1156                total: u64,
1157                watch_matches: u64,
1158            ) {
1159                let s: &mut S = &mut self.on_close;
1160                s(query, reason, total, watch_matches)
1161            }
1162        }
1163        pub fn on_point_then(
1164            mut handle_point: impl FnMut(&dyn Point) -> crate::ShouldBreak,
1165            mut on_close: impl FnMut(StopReason),
1166        ) -> impl PointHandler {
1167            Handle {
1168                on_point: move |point: &dyn Point, _watch: Option<&WatchIDRef>| {
1169                    if (handle_point)(point) {
1170                        ControlFlow::Break(())
1171                    } else {
1172                        ControlFlow::Continue(())
1173                    }
1174                },
1175                on_close: move |_, r, _, _| (on_close)(r),
1176            }
1177        }
1178
1179        #[derive(Copy, Clone)]
1180        struct OnPoint<A>(A);
1181        /// takes a `fn(&dyn Point,Option<&[u8]>, &Linkspace) -> ShouldBreak` and returns impl [PointHandler]
1182        pub fn on_point(
1183            mut handle_point: impl FnMut(&dyn Point) -> crate::ShouldBreak,
1184        ) -> impl PointHandler {
1185            OnPoint(move |point: &dyn Point, _watch: Option<&WatchIDRef>| {
1186                if (handle_point)(point) {
1187                    ControlFlow::Break(())
1188                } else {
1189                    ControlFlow::Continue(())
1190                }
1191            })
1192        }
1193
1194        /// takes any fn(&dyn Point,&Linkspace) -> Try (e.g. Result or Option) and returns impl [PointHandler] that logs on break
1195        pub fn try_on_point<F, E>(mut handle_point: F) -> impl PointHandler
1196        where
1197            E: std::fmt::Debug,
1198            F: for<'a> FnMut(&'a dyn Point) -> Result<(), E> + 'static,
1199        {
1200            OnPoint(move |point: &dyn Point, _watch: Option<&WatchIDRef>| {
1201                (handle_point)(point)
1202                    .branch()
1203                    .map_break(|brk| tracing::error!(?brk, "break"))
1204            })
1205        }
1206
1207        impl<A> PointHandler for OnPoint<A>
1208        where
1209            A: FnMut(&dyn Point, Option<&WatchIDRef>) -> ControlFlow<()>,
1210        {
1211            fn on_point(
1212                &mut self,
1213                point: &dyn Point,
1214                watch: Option<&WatchIDRef>,
1215            ) -> ControlFlow<()> {
1216                (self.0)(point, watch)
1217            }
1218        }
1219
1220        impl<F, R: Try<Output = (), Residual = E>, E: std::fmt::Debug> PointHandler for F
1221        where
1222            F: FnMut(&dyn Point, Option<&WatchIDRef>) -> R + 'static,
1223        {
1224            fn on_point(
1225                &mut self,
1226                point: &dyn Point,
1227                watch: Option<&WatchIDRef>,
1228            ) -> ControlFlow<()> {
1229                match (self)(point, watch).branch() {
1230                    ControlFlow::Continue(_) => ControlFlow::Continue(()),
1231                    ControlFlow::Break(e) => {
1232                        tracing::error!(?e, "break");
1233                        ControlFlow::Break(())
1234                    }
1235                }
1236            }
1237        }
1238
1239        impl<F, S, R: Try<Output = (), Residual = E>, E> PointHandler for (F, S)
1240        where
1241            F: FnMut(&dyn Point, Option<&WatchIDRef>) -> R + 'static,
1242            S: FnMut(&LkQuery, StopReason, u64, u64) + 'static,
1243        {
1244            fn on_point(
1245                &mut self,
1246                point: &dyn Point,
1247                watch: Option<&WatchIDRef>,
1248            ) -> ControlFlow<()> {
1249                match (self.0)(point, watch).branch() {
1250                    ControlFlow::Continue(_) => ControlFlow::Continue(()),
1251                    ControlFlow::Break(_e) => ControlFlow::Break(()),
1252                }
1253            }
1254            fn on_close(&mut self, query: &LkQuery, reason: StopReason, total: u64, writen: u64) {
1255                (self.1)(query, reason, total, writen)
1256            }
1257        }
1258    }
1259}
1260
1261/// common 'opinionated' functions and protocols in linkspace
1262pub mod commons;
1263pub mod experimental;
1264
1265mod argument_traits;
1266#[cfg(any(feature = "pyo3", feature = "wasm"))]
1267pub mod ffi;
1268pub use consts::{PRIVATE, PUBLIC};
1269pub mod consts {
1270    pub use linkspace_core::consts::point_consts::*;
1271    pub use linkspace_core::consts::{EXCHANGE_DOMAIN, PRIVATE, PUBLIC, TEST_GROUP, test_key};
1272}
1273
1274/// misc functions & tools - less stable
1275pub mod misc {
1276    pub use linkspace_core::point::FieldEnum;
1277    pub use linkspace_core::point::RecvPoint;
1278    pub use linkspace_core::point::point::DEFAULT_ROUTING_BITS;
1279    pub use linkspace_core::point::point::cmp::PointCmp;
1280    pub use linkspace_core::point::reroute::{MutXHeaderPoint, SharePointArc};
1281    pub use linkspace_core::point::space_order::SpaceDBEntry;
1282    pub use linkspace_core::point::{calc_free_space, deserialize::read_point_len};
1283    pub use misc_utils as utils;
1284
1285    pub use linkspace_core::stamp_fmt::START_STAMP;
1286
1287    use linkspace_core::prelude::B64;
1288
1289    /// Blake3 hash
1290    pub fn blake3(val: &[u8]) -> B64<[u8; 32]> {
1291        B64(*linkspace_core::crypto::blake3(val).as_bytes())
1292    }
1293
1294    /**
1295    Read bytes as a [0,1) float by reading the first 52 bits.
1296    Panics if fewer than 8 bytes are supplied
1297    Can be used to produces a shared 'random' value by using Point::hash or [blake3] as input.
1298     */
1299    pub fn bytes2uniform(val: &[u8]) -> f64 {
1300        let rand_u64 = u64::from_be_bytes(
1301            val[..8]
1302                .try_into()
1303                .expect("bytes2uniform requires at least 8 bytes"),
1304        );
1305        let f64_exponent_bits: u64 = 1023u64 << 52;
1306        // Generate a value in the range [1, 2)
1307        let value1_2 = f64::from_bits((rand_u64 >> (64 - 52)) | f64_exponent_bits);
1308        value1_2 - 1.0
1309    }
1310
1311    pub static MARK_SVG: &str = include_str!("./mark.svg");
1312}
1313
1314pub use abe::lka_eval;
1315/** ascii byte expression utilities
1316
1317ABE is a byte templating language.
1318See guide#ABE for more info.
1319 **/
1320pub mod abe {
1321    use super::*;
1322    use linkspace_core::abe::abtxt::as_abtxt;
1323    pub use lkp::repr::DEFAULT_POINT_EXPR;
1324
1325    /**
1326    Evaluate an expression to bytes.
1327
1328    Print a list of functions by using `lka_eval("[help]")`
1329
1330    Optionally add a `point` in the scope.
1331    See [lka_eval_strict] that is less strict on its input
1332
1333    ```
1334    # use linkspace::{*,prelude::*,abe::*};
1335    # fn main() -> LkResult{
1336    assert_eq!( b"abc" as &[u8]    , lka_eval( "abc" ,())?, );
1337    assert_eq!( &[0u8,1,255] as &[u8], lka_eval( r#"\0\x01\xff"# ,())?,);
1338
1339    // calling functions such as 'u8'
1340    assert_eq!( b"abc" as &[u8]    , lka_eval( "ab[u8:99]" ,())?, );
1341
1342    assert_eq!(
1343               b"The '' function returns its first argument" as &[u8],
1344       lka_eval( "The '' function returns its first [:argument]", ())?
1345    );
1346
1347    assert_eq!(
1348               b"Bytes are joined" as &[u8],
1349       lka_eval( "Bytes[: are][: joined]" , ())?
1350    );
1351
1352    let result = lka_eval( "Nest expressions [u8:65] == [u8:[:65]] == \x41" , ())?;
1353    assert_eq!(result,   b"Nest expressions A == A == A");
1354
1355    let result = lka_eval( "Use result as first argument with '/' [u8:65] == [:65/u8] == \x41" , ())?;
1356    assert_eq!(result,   b"Use result as first argument with '/' A == A == A");
1357
1358
1359    let result = lka_eval( "You can provide an argv [0] [1]" , &[b"like" as &[u8], b"this"])?;
1360    assert_eq!(result,   b"You can provide an argv like this");
1361
1362    let lp : PointBox = lk_linkpoint(&[], b"mydomain", &[])?;
1363    let point: &dyn Point = &lp;
1364
1365    assert_eq!( lka_eval( "[hash]" , point)?,&*point.hash());
1366    let by_arg   = lka_eval( "[hash:str]", point)?;
1367    let by_apply = lka_eval( "[hash/?b]",  point)?;
1368    let as_field = point.hash().b64().into_bytes();
1369    assert_eq!( by_arg, by_apply);
1370    assert_eq!( by_arg, as_field);
1371
1372    // or provide both at once with (point,&[b"argv"])
1373
1374    // escaped characters
1375    assert_eq!( lka_eval( r#"\n\t\:\/\\\[\]"# ,())?,  &[b'\n',b'\t',b':',b'/',b'\\',b'[',b']'] );
1376
1377    # Ok(())
1378    # }
1379    ```
1380
1381    **/
1382    pub fn lka_eval(expr: &str, scope: &dyn LkEnv) -> LkResult<Vec<u8>> {
1383        _lka_eval(expr, false, scope)
1384    }
1385
1386    /**
1387    lka_eval and String::from_utf8
1388    **/
1389    pub fn lka_eval2str(expr: &str, scope: &dyn LkEnv) -> LkResult<String> {
1390        Ok(String::from_utf8(lka_eval(expr, scope)?)?)
1391    }
1392
1393    /**
1394    Same as lka_eval but accepts bytes outside the range 0x20..0xfe as-is.
1395    useful for templating with newlines and utf bytes.
1396
1397    This distinction exists because UTF has a bunch of characters that can hide a surprise -
1398        lka_eval input and lka_encode output is only every ascii.
1399    ```
1400    # use linkspace::{*,prelude::*,abe::*};
1401    # fn main() -> LkResult{
1402    assert_eq!( "abc 🔗🔗".as_bytes() as &[u8], &lka_eval_strict( "abc 🔗🔗" ,())?, );
1403    assert_eq!( "\0\0\0\0\0\0\0\0\0\0\0\0🔗 4036990103".as_bytes() as &[u8], &lka_eval_strict( "[a:🔗] [:🔗/?u]",())?, );
1404    # Ok(())}
1405    ```
1406    **/
1407    pub fn lka_eval_strict(expr: &str, scope: &dyn LkEnv) -> LkResult<Vec<u8>> {
1408        _lka_eval(expr, true, scope)
1409    }
1410    #[doc(hidden)]
1411    pub fn _lka_eval(expr: &str, strict: bool, scope: &dyn LkEnv) -> LkResult<Vec<u8>> {
1412        Ok(experimental::lka_eval_list(expr, strict, scope)?.concat())
1413    }
1414    /**
1415    encode bytes as an abe that evaluate back to bytes.
1416
1417    accepts a list of func to try and encode.
1418    This function can also be used as the evaluator '[/?:..]'.
1419    ```
1420    # use linkspace::{*,abe::*};
1421    # fn main() -> LkResult{
1422    let bytes= lka_eval("[u32:8]",())?;
1423    assert_eq!(bytes,&[0,0,0,8]);
1424    assert_eq!(lka_encode(&bytes,"/:", &()), r#"\0\0\0\x08"#);
1425    assert_eq!(lka_encode(&bytes,"u32", &()), "[u32:8]");
1426
1427
1428    // the options are a list of '/' separated functions
1429    // In this example 'u32' won't fit, LNS '#' lookup will succeed, if not the encoding would be base64
1430
1431    let public_grp = PUBLIC;
1432    assert_eq!(lka_encode(&*public_grp,"u32/#/b", &()), "[#:pub]");
1433
1434    // We can get meta - encode is also available as a scope during lka_eval
1435
1436    // As the '?' function - with the tail argument being a single reverse option
1437    assert_eq!(lka_eval(r#"[?:\0\0\0[u8:8]:u32]"#,())?,b"[u32:8]");
1438    assert_eq!(lka_eval(r#"[:\0\0\0[u8:8]/?:u32]"#,())?,b"[u32:8]");
1439
1440    // Or as the '?' macro
1441    assert_eq!(lka_eval(r#"[/?:\0\0\0[u8:8]/u32/#/b]"#,())?,b"[u32:8]");
1442
1443    # Ok(())
1444    # }
1445    ```
1446    encode doesn't error, it falls back on plain abtxt.
1447    this variant also swallows bad options, see [lk_try_encode] to avoid doing so.
1448    **/
1449    pub fn lka_encode(bytes: impl AsRef<[u8]>, options: &str, scope: &dyn LkEnv) -> String {
1450        let bytes = bytes.as_ref();
1451        try_encode_impl(bytes, options, true, scope)
1452            .unwrap_or_else(|_v| as_abtxt(bytes).to_string())
1453    }
1454    /// encode as utf8 and escape only invalid byte sequences and '[' ']'.
1455    pub fn lka_encode_utf8(bytes: &[u8]) -> String {
1456        linkspace_core::abe::abtxt::escape_expressions_loose(bytes).to_string()
1457    }
1458    /** [lka_encode] with Err on:
1459    - wrong options
1460    - no result ( use a /: to fallback to abtxt)
1461    - if !ignore_encoder_err on any encoder error
1462    **/
1463    pub fn lka_try_encode(
1464        bytes: impl AsRef<[u8]>,
1465        options: &str,
1466        ignore_encoder_err: bool,
1467        scope: &dyn LkEnv,
1468    ) -> LkResult<String> {
1469        let bytes = bytes.as_ref();
1470        try_encode_impl(bytes, options, ignore_encoder_err, scope)
1471    }
1472    fn try_encode_impl(
1473        bytes: &[u8],
1474        options: &str,
1475        ignore_encoder_err: bool,
1476        scope: &dyn LkEnv,
1477    ) -> LkResult<String> {
1478        Ok(linkspace_core::abe::eval::encode(
1479            &scope.as_scope().as_dyn(),
1480            bytes,
1481            options,
1482            ignore_encoder_err,
1483        )?)
1484    }
1485    pub fn lka_space_expr(expr: &str, scope: &dyn LkEnv) -> LkResult<Space> {
1486        let qexpr: linkspace_core::space_expr::SpaceExpr = expr.parse()?;
1487        qexpr.eval(scope.as_scope().as_dyn())
1488    }
1489}
1490
1491// Allow for interop when using the linkspace_core crate
1492#[doc(hidden)]
1493pub mod interop;
1494
1495/// build a custom scope for ABE for use in [varscope]
1496pub mod work_env {
1497
1498    use core::fmt;
1499    use std::sync::Arc;
1500
1501    use either::Either;
1502    use linkspace_commons_internal::{argon2_identity::eval::AdhocKey, rt_scope::RtScopeSet};
1503    use linkspace_core::abe::eval::{AsScope, AsScopeSet, Scope, ScopeSet};
1504    use linkspace_core::abe::scope::ArgV;
1505    use linkspace_core::eval::thread_local_scope::ScopeLocalLkEnv;
1506    use linkspace_core::eval::{CORE_SCOPE, CoreScope};
1507    use linkspace_core::point::{Domain, GroupID, LkIdentity, PointScope, PubKey};
1508    use linkspace_core::prelude::Point;
1509
1510    #[derive(Copy, Clone, Default, Debug)]
1511    #[repr(C)]
1512    /// Data set by user for custom scopes
1513    pub struct LkEnvData<'o> {
1514        /// Set the point in scope - sets scopes like \[hash:str\], \[group\], \[domain:str]\
1515        pub point: Option<&'o dyn Point>,
1516        /// Add the argv scope e.g. "\[0\] and \[1\]"
1517        pub argv: Option<&'o [&'o [u8]]>,
1518
1519        /// set the working domain, usable as the AB expr [wd]
1520        pub domain: Option<&'o Domain>,
1521        /// set the working group, usable as the AB expr [wg]
1522        pub group: Option<&'o GroupID>,
1523        /// set the working pubkey, usable as the AB expr [wpubkey] - this is not the same as key:pub
1524        pub pubkey: Option<&'o PubKey>,
1525
1526        /// set the default signing key - this is unrelated to the pubkey
1527        pub key: Option<&'o LkIdentity>,
1528
1529        #[cfg(any(feature = "lmdb", feature = "inmem"))]
1530        pub lks: Option<&'o super::LkSystem>,
1531    }
1532
1533    #[derive(Clone, Debug)]
1534    pub struct LkEnvDataSync {
1535        pub point: Option<PointArc>,
1536        pub argv: Option<Arc<Vec<Vec<u8>>>>,
1537        pub domain: Option<Domain>,
1538        pub group: Option<GroupID>,
1539        pub pubkey: Option<PubKey>,
1540        pub key: Option<LkIdentity>,
1541        #[cfg(any(feature = "lmdb", feature = "inmem"))]
1542        pub lksi: Option<LksInfo>,
1543    }
1544
1545    use crate::prelude::*;
1546    use crate::system::{LksInfo, lks_info};
1547    use std::{
1548        cell::{Cell, LazyCell},
1549        ptr::NonNull,
1550    };
1551
1552    use linkspace_core::thread_local as tl;
1553
1554    /// Will panic if an invalid LKE_DOMAIN expr is set - otherwise defaults to ""
1555    pub fn lke_domain() -> Domain {
1556        tl::get_domain(LkaScope::default().as_dyn())
1557    }
1558    pub fn lke_set_domain(domain: Domain) {
1559        let _ = tl::set_domain(Some(domain), true);
1560    }
1561
1562    /// Will panic if an invalid LKE_GROUP expr is set - otherwise defaults to "[#:pub]"
1563    pub fn lke_group() -> GroupID {
1564        tl::get_group(LkaScope::default().as_dyn())
1565    }
1566    pub fn lke_set_group(group: GroupID) {
1567        let _ = tl::set_group(Some(group), true);
1568    }
1569    /// (Not to be confused with lka_eval("[keypub]") ) - Will panic if an invalid LK_PUBKEY expr is set - otherwise defaults to "[@:0]"
1570    pub fn lke_pubkey() -> PubKey {
1571        tl::get_pubkey(LkaScope::default().as_dyn())
1572    }
1573    pub fn lke_key() -> Option<LkIdentity> {
1574        tl::get_key()
1575    }
1576    pub fn lke_set_key(id: LkIdentity) {
1577        let _ = tl::set_key(Some(id), true);
1578    }
1579    /// The open signing key's pubkey, i.e. [get_key].pubkey or lka_eval("[keypub]")
1580    pub fn lke_keypub() -> Option<PubKey> {
1581        tl::get_keypub()
1582    }
1583
1584    // we must not leak the lifetimes from the thread locals
1585    pub fn lke_with<A>(cb: impl FnOnce(crate::work_env::LkEnvData) -> A) -> A {
1586        let point: Option<&dyn Point> = match unsafe { &*crate::work_env::ENV_POINT.as_ptr() } {
1587            None => None,
1588            Some(Either::Left(l)) => Some(l),
1589            Some(Either::Right(l)) => Some(unsafe { l.as_ref() }),
1590        };
1591        let argv: Option<&[&[u8]]> = match unsafe { &*crate::work_env::ENV_ARGV.as_ptr() } {
1592            None => None,
1593            Some((bytes, _)) => Some(bytes),
1594        };
1595        use linkspace_system::thread_local as tls;
1596        let lks = tls::opt_get_system();
1597        cb(crate::work_env::LkEnvData {
1598            point,
1599            argv,
1600            group: unsafe { &*tl::GROUP.as_ptr() }.as_ref(),
1601            pubkey: unsafe { &*tl::PUBKEY.as_ptr() }.as_ref(),
1602            domain: unsafe { &*tl::DOMAIN.as_ptr() }.as_ref(),
1603            lks: lks.as_ref().map(crate::LkSystem::from_impl),
1604            key: unsafe { &*tl::SIGNING_KEY.as_ptr() }.as_ref(),
1605        })
1606    }
1607
1608    pub fn lka_set_keyval(key: &[&[u8]], value: &[u8]) -> crate::LkResult<Vec<u8>> {
1609        linkspace_core::thread_local::set_keyval_map(key, value)
1610    }
1611    #[allow(clippy::type_complexity)]
1612    pub fn lka_set_callback(cb: Box<dyn Fn(&[&[u8]], bool) -> crate::LkResult<Option<Vec<u8>>>>) {
1613        linkspace_core::thread_local::set_shared_cb(cb)
1614    }
1615    #[thread_local]
1616    pub static DUMMY_RT: LazyCell<crate::LkSystem> =
1617        LazyCell::new(|| crate::system::lks_open_inmem("_DUMMY_"));
1618
1619    pub fn lke_set(scope: &dyn LkEnv) {
1620        let mut ud = crate::work_env::LkEnvData::default();
1621        scope.userdata(&mut ud);
1622        if let Some(g) = ud.group {
1623            let _ = tl::set_group(Some(*g), true);
1624        }
1625        if let Some(d) = ud.domain {
1626            let _ = tl::set_domain(Some(*d), true);
1627        }
1628        if let Some(p) = ud.pubkey {
1629            let _ = tl::set_pubkey(Some(*p), true);
1630        }
1631        if let Some(p) = ud.key {
1632            let _ = tl::set_key(Some(p.clone()), true);
1633        }
1634        if let Some(d) = ud.point {
1635            ENV_POINT.set(Some(Either::Left(d.as_arc())));
1636        }
1637        if let Some(rt) = ud.lks {
1638            unsafe { set_system(rt.clone().into()) };
1639        }
1640        if let Some(d) = ud.argv {
1641            let argv_bytes = d.concat().into_boxed_slice();
1642            let mut argv: [&[u8]; 10] = [&[]; 10];
1643            let mut slice: &[u8] = &argv_bytes;
1644            for (bytes, dest) in d.iter().zip(argv.iter_mut()) {
1645                let b: &[u8] = &slice[..bytes.len()];
1646                *dest = unsafe { std::slice::from_ptr_range(b.as_ptr_range()) };
1647                slice = &slice[bytes.len()..]
1648            }
1649            ENV_ARGV.set(Some((argv, Some(argv_bytes))));
1650        }
1651    }
1652    /// A Sync lke struct.
1653    pub fn lke_sync() -> LkEnvDataSync {
1654        lke_with(|e| LkEnvDataSync {
1655            point: e.point.map(|p| p.as_arc()),
1656            argv: e
1657                .argv
1658                .map(|o| Arc::new(o.iter().map(|o| o.to_vec()).collect())),
1659            domain: e.domain.copied(),
1660            group: e.group.copied(),
1661            pubkey: e.pubkey.copied(),
1662            key: e.key.cloned(),
1663            lksi: e.lks.map(|i| lks_info(Some(i)).unwrap()),
1664        })
1665    }
1666
1667    #[thread_local]
1668    pub(crate) static ENV_POINT: Cell<Option<Either<PointArc, NonNull<dyn Point>>>> =
1669        Cell::new(None);
1670
1671    #[thread_local]
1672    pub(crate) static ENV_ARGV: Cell<Option<([&'static [u8]; 10], Option<Box<[u8]>>)>> =
1673        Cell::new(None);
1674
1675    pub fn lke_point_set(point: &dyn Point) {
1676        ENV_POINT.set(Some(Either::Left(point.as_arc())));
1677    }
1678    pub fn lke_point_do<A>(point: &dyn Point, fnc: impl FnOnce() -> A) -> A {
1679        let prev = ENV_POINT
1680            .replace(NonNull::new(point as *const dyn Point as *mut dyn Point).map(Either::Right));
1681        let r = fnc();
1682        ENV_POINT.set(prev);
1683        r
1684    }
1685    pub fn lke_point() -> Option<PointArc> {
1686        match ENV_POINT.take() {
1687            None => None,
1688            Some(Either::Left(l)) => {
1689                ENV_POINT.set(Some(Either::Left(l.clone())));
1690                Some(l)
1691            }
1692            Some(Either::Right(r)) => {
1693                let arc = unsafe { r.as_ref().as_arc() };
1694                ENV_POINT.set(Some(Either::Left(arc.clone())));
1695                Some(arc)
1696            }
1697        }
1698    }
1699
1700    /**
1701    Temporarily hoist the user scope (lks,group, domain, and pubkey) into the threadlocals.
1702    */
1703    pub fn lke_do<A>(scope: &dyn LkEnv, fnc: impl FnOnce() -> A) -> A {
1704        let mut ud = crate::work_env::LkEnvData::default();
1705        scope.userdata(&mut ud);
1706
1707        let prev_group = if let Some(g) = ud.group {
1708            tl::set_group(Some(*g), true).unwrap()
1709        } else {
1710            None
1711        };
1712        let prev_domain = if let Some(d) = ud.domain {
1713            tl::set_domain(Some(*d), true).unwrap()
1714        } else {
1715            None
1716        };
1717        let prev_pubkey = if let Some(k) = ud.pubkey {
1718            tl::set_pubkey(Some(*k), true).unwrap()
1719        } else {
1720            None
1721        };
1722        let prev_key = if let Some(k) = ud.key {
1723            tl::set_key(Some(k.clone()), true).unwrap()
1724        } else {
1725            None
1726        };
1727        let prev_argv = if let Some(slices) = ud.argv {
1728            let mut argv = [&[] as &[u8]; 10];
1729            for (bytes, dest) in slices.iter().zip(argv.iter_mut()) {
1730                *dest = unsafe { std::slice::from_ptr_range(bytes.as_ptr_range()) };
1731            }
1732            ENV_ARGV.replace(Some((argv, None)))
1733        } else {
1734            None
1735        };
1736
1737        let prev_point = if let Some(point) = ud.point {
1738            ENV_POINT.replace(
1739                NonNull::new(point as *const dyn Point as *mut dyn Point).map(Either::Right),
1740            )
1741        } else {
1742            None
1743        };
1744        let result = if let Some(lks) = ud.lks {
1745            crate::system::lks_chsys(lks, fnc)
1746        } else {
1747            fnc()
1748        };
1749        if prev_group.is_some() {
1750            let _ = tl::set_group(prev_group, true);
1751        }
1752        if prev_domain.is_some() {
1753            let _ = tl::set_domain(prev_domain, true);
1754        }
1755        if prev_pubkey.is_some() {
1756            let _ = tl::set_pubkey(prev_pubkey, true);
1757        }
1758        if prev_key.is_some() {
1759            let _ = tl::set_key(prev_key, true);
1760        }
1761        if prev_point.is_some() {
1762            ENV_POINT.set(prev_point);
1763        }
1764        if prev_argv.is_some() {
1765            ENV_ARGV.set(prev_argv);
1766        }
1767        result
1768    }
1769
1770    impl Default for LkaScope<'_> {
1771        fn default() -> Self {
1772            LkEnvData::default().into()
1773        }
1774    }
1775    impl fmt::Debug for LkaScope<'_> {
1776        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1777            f.debug_tuple("LkScope").field(&"_").finish()
1778        }
1779    }
1780
1781    #[derive(Clone)]
1782    /// Functions/Macros used in abe evaluation.
1783    pub struct LkaScope<'o>(pub(crate) InlineScope<'o>);
1784    #[derive(Clone)]
1785    pub(crate) enum InlineScope<'o> {
1786        Std(StdScope<'o>),
1787        Core,
1788        Dyn(&'o dyn Scope),
1789    }
1790    type StdScope<'o> = ScopeSet<LkEnvData<'o>>;
1791
1792    impl<'o> AsScopeSet for LkEnvData<'o> {
1793        type Scope = (
1794            (Option<ScopeSet<PointScope<'o>>>, Option<AsScope<ArgV<'o>>>),
1795            (
1796                Option<AsScope<ScopeLocalLkEnv<'o>>>,
1797                CoreScope,
1798                AsScope<AdhocKey>,
1799            ),
1800            ScopeSet<RtScopeSet<SystemOrThreadLocal<'o>>>,
1801        );
1802        fn as_scope_set(&self) -> Self::Scope {
1803            let mut workenv = None;
1804            if self.domain.is_some()
1805                || self.group.is_some()
1806                || self.pubkey.is_some()
1807                || self.key.is_some()
1808            {
1809                workenv = Some(AsScope(ScopeLocalLkEnv {
1810                    domain: self.domain,
1811                    group: self.group,
1812                    pubkey: self.pubkey,
1813                    key: self.key,
1814                }));
1815            }
1816            let point_scope = self
1817                .point
1818                .or_else(|| match unsafe { &*crate::work_env::ENV_POINT.as_ptr() } {
1819                    None => None,
1820                    Some(Either::Left(l)) => Some(l),
1821                    Some(Either::Right(l)) => Some(unsafe { l.as_ref() }),
1822                })
1823                .map(linkspace_core::point::point_scope);
1824            let argv_scope = self
1825                .argv
1826                .or_else(|| match unsafe { &*crate::work_env::ENV_ARGV.as_ptr() } {
1827                    None => None,
1828                    Some((bytes, _)) => Some(bytes),
1829                })
1830                .map(|bytes| AsScope(ArgV(bytes)));
1831            let lks = ScopeSet(RtScopeSet(SystemOrThreadLocal(self.lks.map(|o| &o.0))));
1832            (
1833                (point_scope, argv_scope),
1834                (workenv, CORE_SCOPE, AsScope(AdhocKey)),
1835                lks,
1836            )
1837        }
1838    }
1839
1840    // This intermediate  struct exists to allow expanding UserData with scope instances
1841    // FIXME: this logic is backwards to support the cli.
1842    impl<'o> LkaScope<'o> {
1843        pub fn core() -> LkaScope<'static> {
1844            LkaScope(InlineScope::Core)
1845        }
1846        pub(crate) fn as_dyn(&self) -> &(dyn Scope + 'o) {
1847            match &self.0 {
1848                InlineScope::Std(scope) => scope,
1849                InlineScope::Dyn(scope) => scope,
1850                InlineScope::Core => &CORE_SCOPE,
1851            }
1852        }
1853        #[doc(hidden)]
1854        pub fn from_dyn(sdyn: &'o dyn Scope) -> Self {
1855            LkaScope(InlineScope::Dyn(sdyn))
1856        }
1857    }
1858    use linkspace_system::thread_local::{SystemOrThreadLocal, set_system};
1859}