# olai-store
Generic, TAO-inspired resource store for typed objects and associations.
`olai-store` provides the async storage abstractions used by services built with the
[Trestle](https://github.com/open-lakehouse/trestle) framework. It defines the core
traits and types for a graph-based resource store: objects (nodes), associations
(edges), field-role enforcement, and secret management.
## Add to your project
```toml
[dependencies]
olai-store = "0.1"
# Enable sqlx::FromRow derives on Object<L>:
# olai-store = { version = "0.1", features = ["sqlx"] }
```
## Key concepts
### `Label`
A `Label` is a type-safe discriminant for resource types — typically an enum generated
by `olai-codegen` from `google.api.resource` proto annotations:
```rust
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum MyLabel { Catalog, Schema, Table }
impl olai_store::Label for MyLabel {}
```
### `Object<L>` and `Association<L>`
`Object<L>` is the untyped interchange format between your store backend and typed
resource structs: a UUID, a label (resource type), a hierarchical `ResourceName`, and
a JSON properties blob.
`Association<L>` is a directed edge between two objects — a UUID, source `from_id`,
edge label string, target `to_id`, and optional JSON properties.
### `ResourceName`
A slash-separated hierarchical identifier, e.g. `"catalogs/my-catalog/schemas/my-schema"`.
The store uses `ResourceName` as the stable human-readable key for each object.
## Store traits
The crate exposes read/write split interfaces:
```rust
// Read-only
ObjectStoreReader<L> — get, get_by_name, list
AssociationStoreReader<L> — list
// Read-write (extends the reader traits)
ObjectStore<L> — create, update, delete
AssociationStore<L> — add, remove
```
A minimal in-memory implementation:
```rust,ignore
use olai_store::{Label, Object, ObjectStore, ObjectStoreReader, ResourceName, Result};
use async_trait::async_trait;
use uuid::Uuid;
struct InMemoryStore<L> { /* ... */ }
#[async_trait]
impl<L: Label> ObjectStoreReader<L> for InMemoryStore<L> {
async fn get(&self, id: &Uuid) -> Result<Object<L>> { /* ... */ }
async fn get_by_name(&self, label: L, name: &ResourceName) -> Result<Object<L>> { /* ... */ }
async fn list(
&self, label: L, namespace: Option<&ResourceName>,
max_results: Option<usize>, page_token: Option<String>,
) -> Result<(Vec<Object<L>>, Option<String>)> { /* ... */ }
}
```
## `ManagedObjectStore` — field-role enforcement
`ManagedObjectStore<L, S, M>` wraps any `ObjectStore<S>` and enforces field roles
derived from proto annotations:
| `Data` | (default) | Stored in properties JSON, returned as-is |
| `Identifier` | `field_behavior = IDENTIFIER` | Stripped on write; mapped to `Object.id` |
| `Managed` | `OUTPUT_ONLY` + known name | Stripped on write; injected on read (`created_at`, `updated_at`, etc.) |
| `Sensitive` | `debug_redact = true` | Routed to `SecretManager` on write; redacted on read |
```rust,ignore
use olai_store::{ManagedObjectStore, NoSecrets, ResourceRegistry};
let registry = ResourceRegistry::from_static(&RESOURCE_DESCRIPTORS);
let store = ManagedObjectStore::new(backend, registry);
// With secret management:
let store = ManagedObjectStore::with_secrets(backend, my_secret_manager, registry);
```
## `ResourceRegistry` and `FieldRole`
`ResourceRegistry<L>` is a runtime map from label → `ResourceTypeDescriptor`, holding
the field roles for every resource type. It is typically constructed from a
`RESOURCE_DESCRIPTORS` static generated by `olai-codegen`:
```rust,ignore
let registry = ResourceRegistry::from_static(&RESOURCE_DESCRIPTORS);
let descriptor = registry.descriptor(MyLabel::Catalog).unwrap();
for field in descriptor.fields {
println!("{}: {:?}", field.name, field.role);
}
```
## `SecretManager`
`SecretManager` is a trait for encrypted storage of sensitive fields. Implement it to
route `Sensitive`-role field values to a key management service or vault:
```rust,ignore
use olai_store::SecretManager;
use async_trait::async_trait;
use uuid::Uuid;
struct MyVault { /* ... */ }
#[async_trait]
impl SecretManager for MyVault {
async fn put(&self, object_id: Uuid, field: &str, value: String) -> olai_store::Result<()> { /* ... */ }
async fn get(&self, object_id: Uuid, field: &str) -> olai_store::Result<Option<String>> { /* ... */ }
async fn delete(&self, object_id: Uuid, field: &str) -> olai_store::Result<()> { /* ... */ }
}
```
Use `NoSecrets` (the default) when you do not need secret management — sensitive fields
are stripped from properties but not stored anywhere.
## Optional `sqlx` feature
```toml
olai-store = { version = "0.1", features = ["sqlx"] }
```
Enables `sqlx::FromRow` on `Object<L>`, making it straightforward to read rows from a
SQL database directly into the store's interchange type.
## Integration with `olai-codegen`
In a typical Trestle project, `olai-codegen` (the `proto-gen` CLI) generates:
- An `ObjectLabel` enum that implements `Label`
- A `RESOURCE_DESCRIPTORS` static slice of `ResourceTypeDescriptor<ObjectLabel>`
Feed these directly into `ResourceRegistry::from_static` and you get full field-role
enforcement with zero boilerplate.
See the [`olai-codegen`](https://crates.io/crates/olai-codegen) crate for the full
code-generation pipeline.
## License
Apache-2.0