Skip to content

Fix property namespace encapsulation - normalize flat properties on read/write #838

Description

@malibio

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)

  1. Write path: Stores flat properties at root level instead of namespace
  2. Read path: Returns raw namespaced structure instead of flattening
  3. 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

  • Write path: flat properties input → namespaced storage
  • Read path: namespaced storage → flat properties output
  • Frontend accesses node.properties.status (no namespace)
  • Type changes preserve dormant properties (invisible to client)
  • All existing tests pass
  • New tests for round-trip (flat in → namespaced storage → flat out)

Benefits

  1. Clean API - Clients work with node.properties.fieldName
  2. Type-change safety - Namespace isolation prevents conflicts (internal detail)
  3. Lossless type switching - Change task→text→task, properties restored
  4. Future-proof - Can change storage strategy without API changes

Related Issues

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions