Skip to content

[FEATURE]: Define vault/storage layer API - note CRUD, metadata, and event contracts #20

@sharma-sugurthi

Description

@sharma-sugurthi

Feature and its Use Cases

SmartNotes currently has no defined storage/vault layer. Several PRs (notably #16 for hybrid search and the AI architecture in docs/ai-architecture.md) are building AI modules that need to read notes, enumerate the vault, and react to changes but there is no agreed API for any of this.

without a clear vault interface, every module ends up reaching directly into the filesystem or SQLite in its own way, making the codebase tightly coupled and hard to test.

this issue proposes we define and agree on a clean vault/storage layer API before more
AI and UI modules are built on top of undefined assumptions.

Problem

Currently:

  • AI modules (chunking, embedding, indexing) have no standard way to enumerate notes
  • there is no event system for note create / update / delete - so incremental indexing cannot be triggered correctly
  • modules reach into raw filesystem paths or storage internals directly
  • there is no metadata API (note ID, title, path, last-modified, tags, etc.)

this is explicitly called out as a concern in docs/ai-architecture.md:

"AI modules should consume these APIs, not reach directly into SQLite schemas or
filesystem paths."
"RAG/indexing workflows should use storage events to trigger incremental updates."

Proposed Vault API (Conceptual)

these are starting-point contracts, not a final implementation. The goal of this issue is to discuss and agree on the shape before anyone starts coding.

Note metadata type

interface NoteMetadata {
  id: string;           // stable unique identifier (not path-dependent)
  title: string;
  path: string;         // relative path within the vault
  lastModified: number; // unix timestamp
  tags?: string[];
}

Core read API

interface VaultReader {
  listNotes(): Promise<NoteMetadata[]>;
  readNote(noteId: string): Promise<string>;
  getNoteMetadata(noteId: string): Promise<NoteMetadata>;
}

Write API

interface VaultWriter {
  createNote(title: string, content: string): Promise<NoteMetadata>;
  updateNote(noteId: string, content: string): Promise<void>;
  deleteNote(noteId: string): Promise<void>;
  renameNote(noteId: string, newTitle: string): Promise<void>;
}

Event / change stream API

type VaultEventType = 'created' | 'updated' | 'deleted' | 'renamed';
interface VaultEvent {
  type: VaultEventType;
  noteId: string;
  metadata?: NoteMetadata;
}
interface VaultWatcher {
  on(event: 'change', listener: (event: VaultEvent) => void): void;
  off(event: 'change', listener: (event: VaultEvent) => void): void;
}
// Combined
interface Vault extends VaultReader, VaultWriter, VaultWatcher {}

Additional Context

Why This Matters

  • AI modules only need VaultReader + VaultWatcher - they subscribe to change events and trigger incremental re-indexing
  • UI components use VaultWriter for creating/editing notes
  • Tests can inject a mock Vault, keeping AI logic fully unit-testable without touching the real filesystem
  • Storage can evolve (SQLite today, different backend later) without breaking AI or UI code

Open Questions for Discussion

  1. should noteId be a content hash, a UUID, or a path-derived stable key?
  2. should the watcher use chokidar / fs.watch, or an in-process event emitter triggered by the write API?
  3. where does metadata (title, tags) live - in markdown frontmatter, or a separate sidecar (SQLite/JSON)?
  4. should VaultReader methods be synchronous or always async?

Related

Code of Conduct

  • I have joined the Discord server and will post updates there
  • I have searched existing issues to avoid duplicates

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions