Overview
Issue #794 implemented namespaced properties for type-safe node type changes, but the encapsulation is incomplete. The namespace should be a purely internal storage detail - clients should never know it exists.
Design Principle
Namespace is an internal implementation detail for type-change safety.
Clients interact with flat properties via node.properties. The backend handles namespace translation transparently.
Client View (API Contract)
// What frontend receives from backend
{
id: "abc-123",
nodeType: "task",
content: "My task",
properties: {
status: "open", // ← Flat within properties
priority: "high" // ← No namespace knowledge needed
}
}
// What frontend sends to backend
updateNode(id, { properties: { status: "done" } }) // ← Flat properties
Storage View (Internal)
{
"node_type": "task",
"properties": {
"task": {
"status": "open",
"priority": "high"
},
"text": {} // ← Dormant namespace from previous type (preserved)
}
}
Backend Responsibility
- On write: Accept flat
properties → store under properties[node_type]
- On read: Fetch from
properties[node_type] → return flattened properties to client
Current State (Bug)
- Write path: Stores flat properties at root level instead of namespace
- Read path: Returns raw namespaced structure instead of flattening
- Frontend workaround: Has to know about namespaces (core-plugins.ts:163)
Example of current broken state:
{
"properties": {
"status": "open", // ← Flat (wrong - write path bug)
"task": {
"_schema_version": 1 // ← Namespaced (from schema defaults)
}
}
}
Solution
1. Write Path (create_node, update_node)
Normalize flat input into namespace before storage:
fn normalize_properties_to_namespace(node: &mut Node, fields: &[SchemaField]) {
// Move properties.* into properties[node_type].*
}
2. Read Path (get_node, query_nodes)
Flatten namespaced properties for API response:
fn flatten_properties_for_response(node: &mut Node) {
// Extract properties[node_type].* to properties.* for client
// Remove namespace wrappers from response
}
Files to Modify
packages/core/src/services/node_service.rs - Add normalization (write) and flattening (read)
packages/core/src/db/surreal_store.rs - Ensure read path flattens before returning
Acceptance Criteria
Benefits
- Clean API - Clients work with
node.properties.fieldName
- Type-change safety - Namespace isolation prevents conflicts (internal detail)
- Lossless type switching - Change task→text→task, properties restored
- Future-proof - Can change storage strategy without API changes
Related Issues
Overview
Issue #794 implemented namespaced properties for type-safe node type changes, but the encapsulation is incomplete. The namespace should be a purely internal storage detail - clients should never know it exists.
Design Principle
Namespace is an internal implementation detail for type-change safety.
Clients interact with flat properties via
node.properties. The backend handles namespace translation transparently.Client View (API Contract)
Storage View (Internal)
{ "node_type": "task", "properties": { "task": { "status": "open", "priority": "high" }, "text": {} // ← Dormant namespace from previous type (preserved) } }Backend Responsibility
properties→ store underproperties[node_type]properties[node_type]→ return flattenedpropertiesto clientCurrent State (Bug)
Example of current broken state:
{ "properties": { "status": "open", // ← Flat (wrong - write path bug) "task": { "_schema_version": 1 // ← Namespaced (from schema defaults) } } }Solution
1. Write Path (create_node, update_node)
Normalize flat input into namespace before storage:
2. Read Path (get_node, query_nodes)
Flatten namespaced properties for API response:
Files to Modify
packages/core/src/services/node_service.rs- Add normalization (write) and flattening (read)packages/core/src/db/surreal_store.rs- Ensure read path flattens before returningAcceptance Criteria
propertiesinput → namespaced storagepropertiesoutputnode.properties.status(no namespace)Benefits
node.properties.fieldNameRelated Issues